Package org.fife.ui.rsyntaxtextarea

Source Code of org.fife.ui.rsyntaxtextarea.SyntaxScheme$SyntaxSchemeLoader

/*
* 02/26/2004
*
* SyntaxScheme.java - The set of colors and tokens used by an RSyntaxTextArea
* to color tokens.
*
* 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.Graphics2D;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import javax.swing.text.StyleContext;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;


/**
* The set of colors and styles used by an <code>RSyntaxTextArea</code> to
* color tokens.<p>
* You can use this class to programmatically set the fonts and colors used in
* an RSyntaxTextArea, but for more powerful, externalized control, consider
* using {@link Theme}s instead.
*
* @author Robert Futrell
* @version 1.0
* @see Theme
*/
public class SyntaxScheme implements Cloneable, TokenTypes {

  private Style[] styles;

  private static final String VERSION      = "*ver1";


  /**
   * Creates a color scheme that either has all color values set to
   * a default value or set to <code>null</code>.
   *
   * @param useDefaults If <code>true</code>, all color values will
   *        be set to default colors; if <code>false</code>, all colors
   *        will be initially <code>null</code>.
   */
  public SyntaxScheme(boolean useDefaults) {
    styles = new Style[DEFAULT_NUM_TOKEN_TYPES];
    if (useDefaults) {
      restoreDefaults(null);
    }
  }


  /**
   * Creates a default color scheme.
   *
   * @param baseFont The base font to use.  Keywords will be a bold version
   *        of this font, and comments will be an italicized version of this
   *        font.
   */
  public SyntaxScheme(Font baseFont) {
    this(baseFont, true);
  }


  /**
   * Creates a default color scheme.
   *
   * @param baseFont The base font to use.  Keywords will be a bold version
   *        of this font, and comments will be an italicized version of this
   *        font.
   * @param fontStyles Whether bold and italic should be used in the scheme
   *        (vs. all tokens using a plain font).
   */
  public SyntaxScheme(Font baseFont, boolean fontStyles) {
    styles = new Style[DEFAULT_NUM_TOKEN_TYPES];
    restoreDefaults(baseFont, fontStyles);
  }


  /**
   * Changes the "base font" for this syntax scheme.  This is called by
   * <code>RSyntaxTextArea</code> when its font changes via
   * <code>setFont()</code>.  This looks for tokens that use a derivative of
   * the text area's old font (but bolded and/or italicized) and make them
   * use the new font with those stylings instead.  This is desirable because
   * most programmers prefer a single font to be used in their text editor,
   * but might want bold (say for keywords) or italics.
   *
   * @param old The old font of the text area.
   * @param font The new font of the text area.
   */
  void changeBaseFont(Font old, Font font) {
    for (int i=0; i<styles.length; i++) {
      Style style = styles[i];
      if (style!=null && style.font!=null) {
        if (style.font.getFamily().equals(old.getFamily()) &&
            style.font.getSize()==old.getSize()) {
          int s = style.font.getStyle(); // Keep bold or italic
          StyleContext sc = StyleContext.getDefaultStyleContext();
          style.font= sc.getFont(font.getFamily(), s, font.getSize());
        }
      }
    }
  }


  /**
   * Returns a deep copy of this color scheme.
   *
   * @return The copy.
   */
  @Override
  public Object clone() {
    SyntaxScheme shcs = null;
    try {
      shcs = (SyntaxScheme)super.clone();
    } catch (CloneNotSupportedException cnse) { // Never happens
      cnse.printStackTrace();
      return null;
    }
    shcs.styles = new Style[styles.length];
    for (int i=0; i<styles.length; i++) {
      Style s = styles[i];
      if (s!=null) {
        shcs.styles[i] = (Style)s.clone();
      }
    }
    return shcs;
  }


  /**
   * Tests whether this color scheme is the same as another color scheme.
   *
   * @param otherScheme The color scheme to compare to.
   * @return <code>true</code> if this color scheme and
   *         <code>otherScheme</code> are the same scheme;
   *         <code>false</code> otherwise.
   */
  @Override
  public boolean equals(Object otherScheme) {

    // No need for null check; instanceof takes care of this for us,
    // i.e. "if (!(null instanceof Foo))" evaluates to "true".
    if (!(otherScheme instanceof SyntaxScheme)) {
      return false;
    }

    Style[] otherSchemes = ((SyntaxScheme)otherScheme).styles;

    int length = styles.length;
    for (int i=0; i<length; i++) {
      if (styles[i]==null) {
        if (otherSchemes[i]!=null) {
          return false;
        }
      }
      else if (!styles[i].equals(otherSchemes[i])) {
        return false;
      }
    }
    return true;

  }


  /**
   * Returns a hex string representing an RGB color, of the form
   * <code>"$rrggbb"</code>.
   *
   * @param c The color.
   * @return The string representation of the color.
   */
  private static final String getHexString(Color c) {
    return "$" + Integer.toHexString((c.getRGB() & 0xffffff)+0x1000000).
                  substring(1);
  }


  /**
   * Returns the specified style.
   *
   * @param index The index of the style.
   * @return The style.
   * @see #setStyle(int, Style)
   * @see #getStyleCount()
   */
  public Style getStyle(int index) {
    return styles[index];
  }


  /**
   * Returns the number of styles.
   *
   * @return The number of styles.
   * @see #getStyle(int)
   */
  public int getStyleCount() {
    return styles.length;
  }


  /**
   * Used by third party implementors e.g. SquirreL SQL. Most applications do
   * not need to call this method.
   * <p>
   * Note that the returned array is not a copy of the style data; editing the
   * array will modify the styles used by any <code>RSyntaxTextArea</code>
   * using this scheme.
   *
   * @return The style array.
   * @see #setStyles(Style[])
   */
  public Style[] getStyles() {
    return styles;
  }


  /**
   * This is implemented to be consistent with {@link #equals(Object)}.
   * This is a requirement to keep FindBugs happy.
   *
   * @return The hash code for this object.
   */
  @Override
  public int hashCode() {
    // Keep me fast.  Iterating over *all* syntax schemes contained is
    // probably much slower than a "bad" hash code here.
    int hashCode = 0;
    int count = styles.length;
    for (int i=0; i<count; i++) {
      if (styles[i]!=null) {
        hashCode ^= styles[i].hashCode();
        break;
      }
    }
    return hashCode;
  }


  /**
   * Loads a syntax scheme from an input stream.<p>
   *
   * Consider using the {@link Theme} class for saving and loading RSTA
   * styles rather than using this API.
   *
   * @param baseFont The font to use as the "base" for the syntax scheme.
   *        If this is <code>null</code>, a default monospaced font is used.
   * @param in The stream to load from.  It is up to the caller to close this
   *        stream when they are done.
   * @return The syntax scheme.
   * @throws IOException If an IO error occurs.
   */
  public static SyntaxScheme load(Font baseFont, InputStream in)
                  throws IOException {
    if (baseFont==null) {
      baseFont = RSyntaxTextArea.getDefaultFont();
    }
    return SyntaxSchemeLoader.load(baseFont, in);
  }


  /**
   * Loads a syntax highlighting color scheme from a string created from
   * <code>toCommaSeparatedString</code>.  This method is useful for saving
   * and restoring color schemes.<p>
   *
   * Consider using the {@link Theme} class for saving and loading RSTA
   * styles rather than using this API.
   *
   * @param string A string generated from {@link #toCommaSeparatedString()}.
   * @return A color scheme.
   * @see #toCommaSeparatedString()
   */
  public static SyntaxScheme loadFromString(String string) {
    return loadFromString(string, DEFAULT_NUM_TOKEN_TYPES);
  }


  /**
   * Loads a syntax highlighting color scheme from a string created from
   * <code>toCommaSeparatedString</code>.  This method is useful for saving
   * and restoring color schemes.<p>
   *
   * Consider using the {@link Theme} class for saving and loading RSTA
   * styles rather than using this API.
   *
   * @param string A string generated from {@link #toCommaSeparatedString()}.
   * @param tokenTypeCount The number of token types saved in this string.
   *        This should be the number of token types saved by your custom
   *        SyntaxScheme subclass,
   *        or {@link TokenTypes#DEFAULT_NUM_TOKEN_TYPES} if you used the
   *        standard implementation (which most people will).
   * @return A color scheme.
   * @see #loadFromString(String)
   * @see #toCommaSeparatedString()
   */
  public static SyntaxScheme loadFromString(String string,
      int tokenTypeCount) {

    SyntaxScheme scheme = new SyntaxScheme(true);

    try {

      if (string!=null) {

        String[] tokens = string.split(",", -1);

        // Check the version string, use defaults if incompatible
        if (tokens.length==0 || !VERSION.equals(tokens[0])) {
          return scheme; // Still set to defaults
        }

        int tokenCount = tokenTypeCount*7 + 1; // Version string
        if (tokens.length!=tokenCount) {
          throw new Exception(
            "Not enough tokens in packed color scheme: expected " +
            tokenCount + ", found " + tokens.length);
        }

        // Use StyleContext to create fonts to get composite fonts for
        // Asian glyphs.
        StyleContext sc = StyleContext.getDefaultStyleContext();

        // Loop through each token style.  Format:
        // "index,(fg|-),(bg|-),(t|f),((font,style,size)|(-,,))"
        for (int i=0; i<tokenTypeCount; i++) {

          int pos = i*7 + 1;
          int integer = Integer.parseInt(tokens[pos]); // == i
          if (integer!=i)
            throw new Exception("Expected " + i + ", found " +
                      integer);

          Color fg = null; String temp = tokens[pos+1];
          if (!"-".equals(temp)) { // "-" => keep fg as null
            fg = stringToColor(temp);
          }
          Color bg = null; temp = tokens[pos+2];
          if (!"-".equals(temp)) { // "-" => keep bg as null
            bg = stringToColor(temp);
          }

          // Check for "true" or "false" since we don't want to
          // accidentally suck in an int representing the next
          // packed color, and any string != "true" means false.
          temp = tokens[pos+3];
          if (!"t".equals(temp) && !"f".equals(temp))
            throw new Exception("Expected 't' or 'f', found " + temp);
          boolean underline = "t".equals(temp);

          Font font = null;
          String family = tokens[pos+4];
          if (!"-".equals(family)) {
            font = sc.getFont(family,
              Integer.parseInt(tokens[pos+5])// style
              Integer.parseInt(tokens[pos+6]))// size
          }
          scheme.styles[i] = new Style(fg, bg, font, underline);

        }

      }

    } catch (Exception e) {
      e.printStackTrace();
    }

    return scheme;

  }


  void refreshFontMetrics(Graphics2D g2d) {
    // It is assumed that any rendering hints are already applied to g2d.
    for (int i=0; i<styles.length; i++) {
      Style s = styles[i];
      if (s!=null) {
        s.fontMetrics = s.font==null ? null :
                g2d.getFontMetrics(s.font);
      }
    }
  }


  /**
   * Restores all colors and fonts to their default values.
   *
   * @param baseFont The base font to use when creating this scheme.  If
   *        this is <code>null</code>, then a default monospaced font is
   *        used.
   */
  public void restoreDefaults(Font baseFont) {
    restoreDefaults(baseFont, true);
  }


  /**
   * Restores all colors and fonts to their default values.
   *
   * @param baseFont The base font to use when creating this scheme.  If
   *        this is <code>null</code>, then a default monospaced font is
   *        used.
   * @param fontStyles Whether bold and italic should be used in the scheme
   *        (vs. all tokens using a plain font).
   */
  public void restoreDefaults(Font baseFont, boolean fontStyles) {

    // Colors used by tokens.
    Color comment      = new Color(0,128,0);
    Color docComment    = new Color(164,0,0);
    Color markupComment    = new Color(0, 96, 0);
    Color keyword      = Color.BLUE;
    Color dataType      = new Color(0,128,128);
    Color function      = new Color(173,128,0);
    Color preprocessor    = new Color(128,128,128);
    Color operator      = new Color(128, 64, 64);
    Color regex        = new Color(0,128,164);
    Color variable      = new Color(255,153,0);
    Color literalNumber    = new Color(100,0,200);
    Color literalString    = new Color(220,0,156);
    Color error      = new Color(148,148,0);

    // (Possible) special font styles for keywords and comments.
    if (baseFont==null) {
      baseFont = RSyntaxTextArea.getDefaultFont();
    }
    Font commentFont = baseFont;
    Font keywordFont = baseFont;
    if (fontStyles) {
      // WORKAROUND for Sun JRE bug 6282887 (Asian font bug in 1.4/1.5)
      // That bug seems to be hidden now, see 6289072 instead.
      StyleContext sc = StyleContext.getDefaultStyleContext();
      Font boldFont = sc.getFont(baseFont.getFamily(), Font.BOLD,
          baseFont.getSize());
      Font italicFont = sc.getFont(baseFont.getFamily(), Font.ITALIC,
          baseFont.getSize());
      commentFont = italicFont;//baseFont.deriveFont(Font.ITALIC);
      keywordFont = boldFont;//baseFont.deriveFont(Font.BOLD);
    }

    styles[COMMENT_EOL]        = new Style(comment, null, commentFont);
    styles[COMMENT_MULTILINE]      = new Style(comment, null, commentFont);
    styles[COMMENT_DOCUMENTATION]    = new Style(docComment, null, commentFont);
    styles[COMMENT_KEYWORD]      = new Style(new Color(255,152,0), null, commentFont);
    styles[COMMENT_MARKUP]      = new Style(Color.gray, null, commentFont);
    styles[RESERVED_WORD]        = new Style(keyword, null, keywordFont);
    styles[RESERVED_WORD_2]      = new Style(keyword, null, keywordFont);
    styles[FUNCTION]          = new Style(function);
    styles[LITERAL_BOOLEAN]      = new Style(literalNumber);
    styles[LITERAL_NUMBER_DECIMAL_INT= new Style(literalNumber);
    styles[LITERAL_NUMBER_FLOAT]    = new Style(literalNumber);
    styles[LITERAL_NUMBER_HEXADECIMAL= new Style(literalNumber);
    styles[LITERAL_STRING_DOUBLE_QUOTE= new Style(literalString);
    styles[LITERAL_CHAR]        = new Style(literalString);
    styles[LITERAL_BACKQUOTE]      = new Style(literalString);
    styles[DATA_TYPE]        = new Style(dataType, null, keywordFont);
    styles[VARIABLE]          = new Style(variable);
    styles[REGEX]            = new Style(regex);
    styles[ANNOTATION]        = new Style(Color.gray);
    styles[IDENTIFIER]        = new Style(null);
    styles[WHITESPACE]        = new Style(Color.gray);
    styles[SEPARATOR]        = new Style(Color.RED);
    styles[OPERATOR]          = new Style(operator);
    styles[PREPROCESSOR]        = new Style(preprocessor);
    styles[MARKUP_TAG_DELIMITER]    = new Style(Color.RED);
    styles[MARKUP_TAG_NAME]      = new Style(Color.BLUE);
    styles[MARKUP_TAG_ATTRIBUTE]    = new Style(new Color(63,127,127));
    styles[MARKUP_TAG_ATTRIBUTE_VALUE]= new Style(literalString);
    styles[MARKUP_COMMENT]              = new Style(markupComment, null, commentFont);
    styles[MARKUP_DTD]              = new Style(function);
    styles[MARKUP_PROCESSING_INSTRUCTION] = new Style(preprocessor);
    styles[MARKUP_CDATA]        = new Style(new Color(0xcc6600));
    styles[MARKUP_CDATA_DELIMITER]    = new Style(new Color(0x008080));
    styles[MARKUP_ENTITY_REFERENCE]    = new Style(dataType);
    styles[ERROR_IDENTIFIER]      = new Style(error);
    styles[ERROR_NUMBER_FORMAT]    = new Style(error);
    styles[ERROR_STRING_DOUBLE]    = new Style(error);
    styles[ERROR_CHAR]        = new Style(error);

    // Issue #34: If an application modifies TokenTypes to add new built-in
    // token types, we'll get NPEs if not all styles are initialized.
    for (int i=0; i<styles.length; i++) {
      if (styles[i]==null) {
        styles[i] = new Style();
      }
    }

  }


  /**
   * Sets a style to use when rendering a token type.
   *
   * @param type The token type.
   * @param style The style for the token type.
   * @see #getStyle(int)
   */
  public void setStyle(int type, Style style) {
    styles[type] = style;
  }


  /**
   * Used by third party implementors e.g. SquirreL SQL. Most applications do
   * not need to call this method; individual styles can be set via
   * {@link #setStyle(int, Style)}.
   *
   * @param styles The new array of styles to use.  Note that this should
   *        have length of at least
   *        {@link TokenTypes#DEFAULT_NUM_TOKEN_TYPES}.
   * @see #setStyle(int, Style)
   * @see #getStyles()
   */
  public void setStyles(Style[] styles) {
    this.styles = styles;
  }


  /**
   * Returns the color represented by a string.  If the first char in the
   * string is '<code>$</code>', it is assumed to be in hex, otherwise it is
   * assumed to be decimal.  So, for example, both of these:
   * <pre>
   * "$00ff00"
   * "65280"
   * </pre>
   * will return <code>new Color(0, 255, 0)</code>.
   *
   * @param s The string to evaluate.
   * @return The color.
   */
  private static final Color stringToColor(String s) {
    // Check for decimal as well as hex, for backward
    // compatibility (fix from GwynEvans on forums)
    char ch = s.charAt(0);
    return new Color((ch=='$' || ch=='#') ?
        Integer.parseInt(s.substring(1),16) :
        Integer.parseInt(s));
  }


  /**
   * Returns this syntax highlighting scheme as a comma-separated list of
   * values as follows:
   * <ul>
   *   <li>If a color is non-null, it is added as a 24-bit integer
   *      of the form <code>((r<<16) | (g<<8) | (b))</code>; if it is
   *       <code>null</code>, it is added as "<i>-,</i>".
   *   <li>The font and style (bold/italic) is added as an integer like so:
   *       "<i>family,</i> <i>style,</i> <i>size</i>".
   *   <li>The entire syntax highlighting scheme is thus one long string of
   *       color schemes of the format "<i>i,[fg],[bg],uline,[style]</i>,
   *       where:
   *       <ul>
   *          <li><code>i</code> is the index of the syntax scheme.
   *          <li><i>fg</i> and <i>bg</i> are the foreground and background
   *              colors for the scheme, and may be null (represented by
   *              <code>-</code>).
   *          <li><code>uline</code> is whether or not the font should be
   *              underlined, and is either <code>t</code> or <code>f</code>.
   *          <li><code>style</code> is the <code>family,style,size</code>
   *              triplet described above.
   *       </ul>
   * </ul>
   *
   * @return A string representing the rgb values of the colors.
   * @see #loadFromString(String)
   */
  public String toCommaSeparatedString() {

    StringBuilder sb = new StringBuilder(VERSION);
    sb.append(',');

    for (int i=0; i<styles.length; i++) {

      sb.append(i).append(',');

      Style ss = styles[i];
      if (ss==null) { // Only true for i==0 (NULL token)
        sb.append("-,-,f,-,,,");
        continue;
      }

      Color c = ss.foreground;
      sb.append(c!=null ? (getHexString(c) + ",") : "-,");
      c = ss.background;
      sb.append(c!=null ? (getHexString(c) + ",") : "-,");

      sb.append(ss.underline ? "t," : "f,");

      Font font = ss.font;
      if (font!=null) {
        sb.append(font.getFamily()).append(',').
          append(font.getStyle()).append(',').
            append(font.getSize()).append(',');
      }
      else {
        sb.append("-,,,");
      }

    }

    return sb.substring(0,sb.length()-1); // Take off final ','.

  }

  /**
   * Loads a <code>SyntaxScheme</code> from an XML file.
   */
  private static class SyntaxSchemeLoader extends DefaultHandler {

    private Font baseFont;
    private SyntaxScheme scheme;

    public SyntaxSchemeLoader(Font baseFont) {
      scheme = new SyntaxScheme(baseFont);
    }

    public static SyntaxScheme load(Font baseFont, InputStream in)
        throws IOException {
      SyntaxSchemeLoader parser = null;
      try {
        XMLReader reader = XMLReaderFactory.createXMLReader();
        parser = new SyntaxSchemeLoader(baseFont);
        parser.baseFont = baseFont;
        reader.setContentHandler(parser);
        InputSource is = new InputSource(in);
        is.setEncoding("UTF-8");
        reader.parse(is);
      } catch (SAXException se) {
        throw new IOException(se.toString());
      }
      return parser.scheme;
    }

    @Override
    public void startElement(String uri, String localName, String qName,
                Attributes attrs) {

      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(scheme);
          } catch (IllegalArgumentException e) {
            e.printStackTrace();
            return;
          } catch (IllegalAccessException e) {
            e.printStackTrace();
            return;
          }

          String fgStr = attrs.getValue("fg");
          if (fgStr!=null) {
            Color fg = stringToColor(fgStr);
            scheme.styles[index].foreground = fg;
          }

          String bgStr = attrs.getValue("bg");
          if (bgStr!=null) {
            Color bg = stringToColor(bgStr);
            scheme.styles[index].background = bg;
          }

          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; }
            scheme.styles[index].font = baseFont.deriveFont(style);
          }

          String ulineStr = attrs.getValue("underline");
          if (ulineStr!=null) {
            boolean uline= Boolean.valueOf(ulineStr).booleanValue();
            scheme.styles[index].underline = uline;
          }

        }

      }

    }

  }


}
TOP

Related Classes of org.fife.ui.rsyntaxtextarea.SyntaxScheme$SyntaxSchemeLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.