Articles Projects Tips Downloads Contacts About

Custom EditorKit creation example.
By Stanislav Lapitsky

Table of content of the tutorial

There are 7 classes MyEditorKit, MyDocument, MyViewFactory, MyLabelView, MyUnderlineAction, and MyWriter/MyReader to see how each part of the example EditorKit can be customized.

MyEditorKit is a main class also includes example app to see the results. The kit replaces default document with a custom one (MyDocument) which supports applying jagged underline to text region. Also it creates custom ViewFactory (MyFactory). The factory replaces default LabelView with MyLabelView. The latest checks whether element contains the JAGGED_UDERLINE_ATTRIBUTE_NAME and paint the jgged line. Also there is an action which is added to the actions list to apply the custom underline.

The MyDocument used in the EditorKit example can be stored in an XML and loaded from the XML using MyWriter and MyReader accordingly.

import javax.swing.text.*;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.*;
 
public class MyEditorKit extends StyledEditorKit {
    
    public ViewFactory getViewFactory() {
        return new MyViewFactory();
    }
 
    public Document createDefaultDocument() {
        return new MyDocument();
    }
 
    public void write(Writer out, Document doc, int pos, int len)
        throws IOException, BadLocationException {
 
        MyWriter.write(doc, pos, len, out);
    }
 
    public void read(InputStream in, Document doc, int pos)
	    throws IOException, BadLocationException {
 
        MyReader.read(doc, pos, in);
    }
    
    public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
        BufferedReader br=new BufferedReader(in);
        String s=br.readLine();
        StringBuffer buff=new StringBuffer();
        while (s!=null) {
            buff.append(s);
            s=br.readLine();
        }
 
        MyReader.read(doc, pos, new ByteArrayInputStream(buff.toString().getBytes()));
    }
    
    public String getContentType() {
        return "text/myxml";
    }
 
    static class MyViewFactory implements ViewFactory {
        public View create(Element elem) {

            String kind = elem.getName();
            if (kind != null) {
                if (kind.equals(AbstractDocument.ContentElementName)) {
                    return new MyLabelView(elem);
                } else if (kind.equals(AbstractDocument.ParagraphElementName)) {
                    return new ParagraphView(elem);
                } else if (kind.equals(AbstractDocument.SectionElementName)) {
                    return new BoxView(elem, View.Y_AXIS);
                } else if (kind.equals(StyleConstants.ComponentElementName)) {
                    return new ComponentView(elem);
                } else if (kind.equals(StyleConstants.IconElementName)) {
                    return new IconView(elem);
                }

            }
 
            return new LabelView(elem);
        }
    }
    
    public Action[] getActions() {
	    return TextAction.augmentList(super.getActions(), new Action[] {new MyUnderlineAction()});
    }
 
    public static class MyUnderlineAction extends StyledTextAction {
 
        public MyUnderlineAction() {
            super("jagged-underline");
        }
 
        public void actionPerformed(ActionEvent e) {
            JEditorPane editor = getEditor(e);
            if (editor != null) {
                MyDocument doc=(MyDocument)editor.getDocument();
                int start=editor.getSelectionStart();
                int end=editor.getSelectionEnd();
                if (start!=end) {
                    if (start>end) {
                        int tmp=start;
                        start=end;
                        end=tmp;
                    }
 
                    doc.setJaggedUnderline(start, end);
                }

            }
        }
    }
 
    public static void main(String[] args) {
        JFrame frame=new JFrame("Custom EditorKit example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        JEditorPane edit=new JEditorPane();
        initEditor(edit);
        frame.getContentPane().add(new JScrollPane(edit));
 
        frame.setSize(600,400);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
 
    protected static void initEditor(JEditorPane edit) {
        try {
            edit.setEditorKit(new MyEditorKit());
 
            MyDocument doc=(MyDocument)edit.getDocument();
            doc.insertString(0, "This is jagged underline example", null);
            doc.setJaggedUnderline(8,13);
        } catch (BadLocationException e) {
            e.printStackTrace();  
        }
    }
}

import javax.swing.text.*;
 
public class MyDocument extends DefaultStyledDocument {
    public static String JAGGED_UDERLINE_ATTRIBUTE_NAME="jagged-underline";
 
    public MyDocument() {
    }
 
    public void setJaggedUnderline(int startOffset, int endOffset) {
        SimpleAttributeSet attrs=new SimpleAttributeSet();
        attrs.addAttribute(JAGGED_UDERLINE_ATTRIBUTE_NAME, Boolean.TRUE);
        setCharacterAttributes(startOffset, endOffset-startOffset, attrs, false);
    }
}

import javax.swing.text.*;
import java.awt.*;
 
public class MyLabelView extends LabelView {
    
    public MyLabelView(Element elem) {
        super(elem);
    }
    
    public void paint(Graphics g, Shape allocation) {
        super.paint(g, allocation);
        if (getAttributes().getAttribute(MyDocument.JAGGED_UDERLINE_ATTRIBUTE_NAME)!=null &&
            (Boolean)getAttributes().getAttribute(MyDocument.JAGGED_UDERLINE_ATTRIBUTE_NAME)) {
            paintJaggedLine(g, allocation);
        }
    }

 
    public void paintJaggedLine(Graphics g, Shape a) {
        int y = (int) (a.getBounds().getY() + a.getBounds().getHeight());
        int x1 = (int) a.getBounds().getX();
        int x2 = (int) (a.getBounds().getX() + a.getBounds().getWidth());
 
        Color old = g.getColor();
        g.setColor(Color.red);
        for (int i = x1; i <= x2; i += 6) {
            g.drawArc(i + 3, y - 3, 3, 3, 0, 180);
            g.drawArc(i + 6, y - 3, 3, 3, 180, 181);
        }
        g.setColor(old);
    }

}

import javax.swing.text.*;
import java.io.IOException;
import java.io.Writer;
 
public class MyWriter {

    public static void write(Document doc, int start, int len, Writer out) throws IOException, BadLocationException {
        out.write("<">");
        Element root=doc.getDefaultRootElement();
        int iStart=root.getElementIndex(start);
        int iEnd=root.getElementIndex(start+len);
 
        for (int i=iStart; i<=iEnd; i++) {
            Element par=root.getElement(i);
            writePar(par, start, len, out);
        }
        out.write("</"+MyDocument.TAG_NAME_DOCUMENT+">");
    }
 
    public static void writePar(Element par, int start, int len, Writer out) throws IOException, BadLocationException {
        out.write("<" "+MyDocument.ATTR_NAME_ALIGN+"=\""+align+"\"");
        float first=StyleConstants.getFirstLineIndent(par.getAttributes());
        out.write(" "+MyDocument.ATTR_NAME_FIRST+"=\""+first+"\"");
        float above=StyleConstants.getSpaceAbove(par.getAttributes());
        out.write(" "+MyDocument.ATTR_NAME_ABOWE+"=\""+above+"\"");
        float below=StyleConstants.getSpaceBelow(par.getAttributes());
        out.write(" "+MyDocument.ATTR_NAME_BELOW+"=\""+below+"\"");
        float left=StyleConstants.getLeftIndent(par.getAttributes());
        out.write(" "+MyDocument.ATTR_NAME_LEFT+"=\""+left+"\"");
        float right=StyleConstants.getRightIndent(par.getAttributes());
        out.write(" "+MyDocument.ATTR_NAME_RIGHT+"=\""+right+"\"");
        float ls=StyleConstants.getLineSpacing(par.getAttributes());
        out.write(" "+MyDocument.ATTR_NAME_LINE_SPACING+"=\""+ls+"\"");
 
        TabSet ts=StyleConstants.getTabSet(par.getAttributes());
        if (ts!=null) {
            throw new IOException("TabSet saving is not supported!");
        }
 
        out.write(">");
        //write children
        int iStart=par.getElementIndex(start);
        int iEnd=par.getElementIndex(start+len);
 
        for (int i=iStart; i<=iEnd; i++) {
            Element text=par.getElement(i);
            writeText(text, start, len, out);
        }
 
        out.write("</"+MyDocument.TAG_NAME_PAR+">");
    }
    
    public static void writeText(Element text, int start, int len, Writer out) throws IOException, BadLocationException {
        out.write("<" "+MyDocument.ATTR_NAME_FONT_SIZE+"=\""+fs+"\"");
        String name=StyleConstants.getFontFamily(text.getAttributes());
        out.write(" "+MyDocument.ATTR_NAME_FONT_FAMILY+"=\""+name+"\"");
        boolean bold=StyleConstants.isBold(text.getAttributes());
        out.write(" "+MyDocument.ATTR_NAME_BOLD+"=\""+bold+"\"");
        boolean italic=StyleConstants.isItalic(text.getAttributes());
        out.write(" "+MyDocument.ATTR_NAME_ITALIC+"=\""+italic+"\"");
        boolean underline=StyleConstants.isUnderline(text.getAttributes());
        out.write(" "+MyDocument.ATTR_NAME_UNDERLINE+"=\""+underline+"\"");
        if (text.getAttributes().getAttribute(MyDocument.JAGGED_UDERLINE_ATTRIBUTE_NAME)!=null) {
            boolean jUnderline=(Boolean)text.getAttributes().getAttribute(MyDocument.JAGGED_UDERLINE_ATTRIBUTE_NAME);
            out.write(" "+MyDocument.ATTR_NAME_JUNDERLINE+"=\""+jUnderline+"\"");
        }
 
        out.write(">");
        //write text

        int textStart=Math.max(start, text.getStartOffset());
        int textEnd=Math.min(start+len, text.getEndOffset());
        
        //NOTE: the String must be processed to replace e.g <> chars
        String s=text.getDocument().getText(textStart, textEnd-textStart);
        s=escapeForXML(s);
        out.write(s);
 
        out.write("</">");
    }
    
    public static String escapeForXML(String src){
        final StringBuilder res = new StringBuilder();
        for (int i=0; i<src.length(); i++) {
            char c =src.charAt(i);
            if (c == '<'>') {
                res.append(">");
            }
            else if (c == '&') {
                res.append("&amp;");
            }
            else if (c == '\"') {
                res.append(""");
            }
            else if (c == '\t') {
                addChar(9, res);
            }
            else if (c == '!') {
                addChar(33, res);
            }
            else if (c == '#') {
                addChar(35, res);
            }
            else if (c == '$') {
                addChar(36, res);
            }
            else if (c == '%') {
                addChar(37, res);
            }
            else if (c == '\'') {
                addChar(39, res);
            }
            else if (c == '(') {
                addChar(40, res);
            }
            else if (c == ')') {
                addChar(41, res);
            }
            else if (c == '*') {
                addChar(42, res);
            }
            else if (c == '+') {
                addChar(43, res);
            }
            else if (c == ',') {
                addChar(44, res);
            }
            else if (c == '-') {
                addChar(45, res);
            }
            else if (c == '.') {
                addChar(46, res);
            }
            else if (c == '/') {
                addChar(47, res);
            }
            else if (c == ':') {
                addChar(58, res);
            }
            else if (c == ';') {
                addChar(59, res);
            }
            else if (c == '=') {
                addChar(61, res);
            }
            else if (c == '?') {
                addChar(63, res);
            }
            else if (c == '@') {
                addChar(64, res);
            }
            else if (c == '[') {
                addChar(91, res);
            }
            else if (c == '\\') {
                addChar(92, res);
            }
            else if (c == ']') {
                addChar(93, res);
            }
            else if (c == '^') {
                addChar(94, res);
            }
            else if (c == '_') {
                addChar(95, res);
            }
            else if (c == '`') {
                addChar(96, res);
            }
            else if (c == '{') {
                addChar(123, res);
            }
            else if (c == '|') {
                addChar(124, res);
            }
            else if (c == '}') {
                addChar(125, res);
            }
            else if (c == '~') {
                addChar(126, res);
            }
            else if (c == '\n') {
                addChar(10, res);
            }
            else {
                res.append(c);
            }
        }
        return res.toString();
    }
 
    private static void addChar(Integer aIdx, StringBuilder aBuilder){
        String padding = "";
        if( aIdx <= 9 ){
            padding = "00";
        }
        else if( aIdx <= 99 ){
            padding = "0";
        }
        else {
            //no prefix
        }
        String number = padding + aIdx.toString();
        aBuilder.append("&#").append(number).append(";");
    }
 
    public static String unescapeForXML(String src){
        final StringBuilder res = new StringBuilder(src);
        int i=res.indexOf("&");
        while (i>=0) {
            String s=res.substring(i);
            if (s.startsWith("<")) {
                res.replace(i,i+4,"<">")) {
                res.replace(i,i+4,">");
            }
            else if (s.startsWith("&amp;")) {
                res.replace(i,i+5,"&");
            }
            else if (s.startsWith("&qout;")) {
                res.replace(i,i+6,"\"");
            }
            else if (s.startsWith("&#")) {
                int charEnd=res.indexOf(";", i);
                if (charEnd>=0) {
                    String cStr=res.substring(i+2, charEnd);
                    char c=(char)Integer.parseInt(cStr);
                    res.replace(i, charEnd+1, c+"");
                }
            }
 
            i=res.indexOf("&", i+1);
        }

        return res.toString();
    }
 
}

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.xml.sax.SAXException;
 
import javax.swing.text.*;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
 
public class MyReader {
 
    public static void read(Document d, int pos, InputStream in) throws IOException, BadLocationException {
        if (!(d instanceof DefaultStyledDocument)) {
            return;
        }
 
        DefaultStyledDocument doc=(DefaultStyledDocument)d;
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setIgnoringElementContentWhitespace(false);
 
        try {
            //Using factory get an instance of document builder
            javax.xml.parsers.DocumentBuilder dbXML = dbf.newDocumentBuilder();
 
            //parse using builder to get DOM representation of the XML file
            org.w3c.dom.Document dom = dbXML.parse(in);
 
            NodeList pars=dom.getDocumentElement().getChildNodes();
            int offs=pos;
            for (int i=0; i<pars.getLength(); i++) {
                Node par=pars.item(i);
                offs+=writePar(doc, offs, par);
            }

        } catch(SAXException pce) {
            pce.printStackTrace();
            throw new IOException(pce.getMessage());
        } catch(ParserConfigurationException pce) {

            pce.printStackTrace();
            throw new IOException(pce.getMessage());
        }
    }
 
    public static int writePar(DefaultStyledDocument doc, int pos, Node par) throws BadLocationException{
        int len=0;
        NodeList texts=par.getChildNodes();
        for (int i=0; i<texts.getLength(); i++) {
            Node text=texts.item(i);
            len+=writeText(doc, pos+len, text);
        }

        doc.setParagraphAttributes(pos+len-1, 1, getParagraphAttributes(par), true);
 
        return len;
    }
 
    public static SimpleAttributeSet getParagraphAttributes(Node par) {
        SimpleAttributeSet res=new SimpleAttributeSet();
 
        String v=par.getAttributes().getNamedItem(MyDocument.ATTR_NAME_ALIGN).getNodeValue();
        StyleConstants.setAlignment(res, Integer.parseInt(v));
        v=par.getAttributes().getNamedItem(MyDocument.ATTR_NAME_ABOWE).getNodeValue();
        StyleConstants.setSpaceAbove(res, Float.parseFloat(v));
        v=par.getAttributes().getNamedItem(MyDocument.ATTR_NAME_BELOW).getNodeValue();
        StyleConstants.setSpaceBelow(res, Float.parseFloat(v));
        v=par.getAttributes().getNamedItem(MyDocument.ATTR_NAME_LEFT).getNodeValue();
        StyleConstants.setLeftIndent(res, Float.parseFloat(v));
        v=par.getAttributes().getNamedItem(MyDocument.ATTR_NAME_RIGHT).getNodeValue();
        StyleConstants.setRightIndent(res, Float.parseFloat(v));
        v=par.getAttributes().getNamedItem(MyDocument.ATTR_NAME_LINE_SPACING).getNodeValue();
        StyleConstants.setLineSpacing(res, Float.parseFloat(v));
 
        return res;
    }
 
    public static SimpleAttributeSet getCharacterAttributes(Node text) {
        SimpleAttributeSet res=new SimpleAttributeSet();
 
        String v=text.getAttributes().getNamedItem(MyDocument.ATTR_NAME_FONT_SIZE).getNodeValue();
        StyleConstants.setFontSize(res, Integer.parseInt(v));
        v=text.getAttributes().getNamedItem(MyDocument.ATTR_NAME_FONT_FAMILY).getNodeValue();
        StyleConstants.setFontFamily(res, v);
        v=text.getAttributes().getNamedItem(MyDocument.ATTR_NAME_BOLD).getNodeValue();
        StyleConstants.setBold(res, Boolean.parseBoolean(v));
        v=text.getAttributes().getNamedItem(MyDocument.ATTR_NAME_ITALIC).getNodeValue();
        StyleConstants.setItalic(res, Boolean.parseBoolean(v));
        v=text.getAttributes().getNamedItem(MyDocument.ATTR_NAME_UNDERLINE).getNodeValue();
        StyleConstants.setUnderline(res, Boolean.parseBoolean(v));
        if (text.getAttributes().getNamedItem(MyDocument.ATTR_NAME_JUNDERLINE)!=null) {
            v=text.getAttributes().getNamedItem(MyDocument.ATTR_NAME_JUNDERLINE).getNodeValue();
            res.addAttribute(MyDocument.JAGGED_UDERLINE_ATTRIBUTE_NAME,Boolean.parseBoolean(v));
        }
 
        return res;
    }
 
    public static int writeText(DefaultStyledDocument doc, int pos, Node text) throws BadLocationException{
        if (text.getFirstChild()!=null) {
            String s=text.getFirstChild().getNodeValue();
            doc.insertString(pos, s, getCharacterAttributes(text));
            return s.length();
        }
 
        return 0;
    }

}

Back to Table of Content