So when I was asked for a FCU java bean, I started to search how to handle SVG in java and found Batik, which is an SVG toolkit made by the Apache foundation. The principle is quite simple: draw the interface with Inkscape, then group the elements according to the desired behaviour, eg right knob, left knob, auto-pilot button, etc. Then fire up the XML editor and set a unique ID for each group.

The next steps requires your favourite java editor or IDE. I used netbeans, but anything allowing you to type java code is OK. Use it to write a simple class able to load and display a SVG file, using the JSVGCanvas class provided by batik, or by derivation, or by using a class member


public class FCUCanvas extends JSVGCanvas {
    ...
    private SVGDocument doc=null;

    //handles on interactive SVG elements
    private Element altBtn=null;
    private Element spdBtn=null;
    private Element altValue=null;
    private Element spdValue=null;
    private Element ap1Btn=null;
    private Element ap1Light=null;
    private Element spdLight=null;
    private Element altLight=null;
    private PropertyChangeSupport propertySupport=null;
    /** Creates a new instance of FCUCanvas */
    public FCUCanvas() {
        setPreferredSize(new Dimension(500, 300));
        propertySupport = new PropertyChangeSupport( this );
        setDocumentState(JSVGComponent.ALWAYS_DYNAMIC);
        try {
            String parser = XMLResourceDescriptor.getXMLParserClassName();
            SAXSVGDocumentFactory fact = new SAXSVGDocumentFactory(parser);
            URL thisClassURL = ClassLoader.getSystemResource("fcd/fcu.svg");
            doc = (SVGDocument)(fact.createDocument(thisClassURL.toString()));
            setDocument(doc);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        
        //inint graphics when gvt tree is rendered
        addGVTTreeRendererListener(new GVTTreeRendererAdapter() {
            public void gvtRenderingCompleted(GVTTreeRendererEvent e){
                //retreive SVG elements by their IDs
                altBtn=doc.getElementById("altbtn");
                spdBtn=doc.getElementById("spdbtn");
                altValue=doc.getElementById("altvalue");
                spdValue=doc.getElementById("spdvalue");
                ap1Btn=doc.getElementById("ap1btn");
                ap1Light=doc.getElementById("ap1light");
                spdLight=doc.getElementById("spdlight");
                altLight=doc.getElementById("altlight");                        
        ...
    }
...
}

Once the SVG file is loaded, two things have to be done:

  • In order to handle mouse inputs, we must attach event liteners to relevant SVG groups. This is to be added in the gvtRenderingCompleted method:
    ((EventTarget)altBtn).addEventListener("mousedown", new EventListener(){
                        public void handleEvent(org.w3c.dom.events.Event evt) {
                            DOMMouseEvent q = (DOMMouseEvent)evt;
                            short button = q.getButton();
                            if (button == MOUSE_LEFT)
                                incrAlt(400);
                            if (button == MOUSE_RIGHT)
                                incrAlt(-400);
                        } }, false);
                        ((EventTarget)spdBtn).addEventListener("mousedown", new EventListener(){
                            public void handleEvent(org.w3c.dom.events.Event evt) {
                                DOMMouseEvent q = (DOMMouseEvent)evt;
                                short button = q.getButton();
                                if (button == MOUSE_LEFT)
                                    incrSpd(40);
                                if (button == MOUSE_RIGHT)
                                    incrSpd(-40);
                            } }, false);
                            ((EventTarget)ap1Btn).addEventListener("mousedown", new EventListener(){
                                public void handleEvent(org.w3c.dom.events.Event evt) {
                                    toggleAP1();
                                } }, false);
    
    Notre that we are using here the W3C DOM event model and not the JAVA2D one. The advantage is that we can attach the event listener directly to an SVG Element. The disadvantage is that this event model does not handle wheel mouse.
  • Write the methods able to change what is displayed. As inputs can come from two differents threads (from the graphical thread on mouse inputs, or from the java bean thread), we have to make Runnable classes to modify the display.
        private void schedule(Runnable r) {getUpdateManager().getUpdateRunnableQueue().invokeLater(r);}
        
        private class SetVisible implements Runnable {
            private GraphicsNode _n;
            private boolean _b;
            public SetVisible(GraphicsNode n, boolean b) {_n = n; _b = b;}
            public void run() {
                _n.setVisible(_b);
            }
        }
        private class SetText implements Runnable {
            private Element _e;
            private String _s;
            public SetText(Element span, String s) {_e = span; _s = s;}
            public void run() {
                _e.getFirstChild().setNodeValue( _s);
            }
        }
        private class Transform implements Runnable {
            private GraphicsNode _n;
            private AffineTransform _t;
            public Transform(GraphicsNode node, AffineTransform transform) {_n = node; _t=transform;}
            public void run() {
                _n.setTransform(_t);
            }
        }
    
    Note that you must use the Transform class directly on graphics nodes, this way:
    
        private void translate(Element e, double dX, double dY){
            if (e == null) return;
            GraphicsNode graphicsNode = bridgeContext.getGraphicsNode(e);
            AffineTransform t = graphicsNode.getTransform();
            if (t==null)
                t=new AffineTransform();
            t.translate(dX, dY);
            schedule(new Transform(graphicsNode, t));
        }
        private void rotate(Element e, double angle, Element center){
            if (e == null) return;
            GraphicsNode graphicsNode = bridgeContext.getGraphicsNode(e);
            Element c = (center == null) ? e : center;
            Rectangle2D centerBounds = bridgeContext.getGraphicsNode(c).getBounds();
            if (centerBounds == null)
                return;
            AffineTransform t = graphicsNode.getTransform();
            if (t==null)
                t=new AffineTransform();
            t.rotate(angle, centerBounds.getCenterX(), centerBounds.getCenterY());
            schedule(new Transform(graphicsNode, t));
        }
    

OK, we are nearly finished. I remember having mentioned that we were making a java bean, so we miss the set/get methods, at your convenience. Here are a couple, the other one are rigourosly identical:

    public int getSpeedCmd(){return speed;}
    public void setSpeedCmd(int sp){
        propertySupport.firePropertyChange("speedCmd", speed, sp);
        speed = sp;
        setValue(spdValue, sp);
    }
    public boolean getSpeedLight() {return speedLightOn;}
    public void setSpeedLight(boolean b){
        propertySupport.firePropertyChange("speedLight", speedLightOn, b);
        speedLightOn = b;
        setVisible(spdLight, b);
    }

So what's next ? I have been thinking about putting all the behaviour in the SVG, using javascript and/or with behavioral selectors, eg adding a class="2 state button". This way, we could have a unique java class, working with any SVG, able to autogenerate methods using the ID and the class of the SVG elements. Making a java bean would be really easy this way ...