X

Geertjan's Blog

  • January 9, 2012

Jmol Gets a Window System and Loosely Coupled Capabilities

Geertjan Wielenga
Product Manager

As you can see below, Jmol now has a window system, so that multiple molecules can be open simultaneously. Handy for viewing (e.g., the windows can be undocked from the frame of the application and moved around, also outside of the frame onto other monitors) and possibly also for molecular comparisons of various kinds:

But what's even cooler than that is the way the explorers on the left (i.e., the data providers) interact with the editor windows (i.e., the Jmol viewers). Here I've implemented the "capability pattern", which enables the two sides of the relationship to not need to know anything about each other, aside from the capabilities that one side publishes and to which the other side is subscribed.

So, I have two capabilities involved in this particular scenario:

package org.netbeans.jmol.api;
public interface LoadScriptCapability {
String getName();
    String getScript();
}
package org.netbeans.jmol.api;
public interface OpenCapability {
void open();
}

The above two interfaces are in an API module, which the other modules all depend on.

And here's the definition of one of the Nodes you see on the left hand side, i.e., a data provider leaf node representating a single molecule:

public class ProteinNode extends BeanNode {
    private ProteinLoadScriptCapability plsc = new ProteinLoadScriptCapability();
    public ProteinNode(String bean) throws IntrospectionException {
       this(bean, new InstanceContent());
    }
    public ProteinNode(String bean, final InstanceContent ic) throws IntrospectionException {
        super(bean, Children.LEAF, new AbstractLookup(ic));
        setDisplayName(bean);
       ic.add(new OpenCapability() {
            @Override
            public void open() {
                ic.add(plsc);
                ic.remove(plsc);
                StatusDisplayer.getDefault().setStatusText("Opened: " + getDisplayName());
            }
        });
    }
private class ProteinLoadScriptCapability implements LoadScriptCapability {
       @Override
       public String getScript() {
            return "var xid = _modelTitle; if (xid.length != 4) { xid = '"
                    + getDisplayName()
                    + "'};load @{'=' + xid}";
        }

       @Override
        public String getName() {
            return getDisplayName();
        }
    }
@Override
    public Action[] getActions(boolean context) {
        List<? extends Action> jmolActions = Utilities.actionsForPath("Actions/JMol/Leaf");
       return jmolActions.toArray(new Action[jmolActions.size()]);
    }
}

So, as you can see below, the Action (which is on the Node above) will be enabled because its context, which is the Node, has an OpenCapability in its Lookup. When the "open" method is invoked,  which is when the ProteinLoadScriptCapability is added to the Lookup of the Node, which is the selected Node, which thus publishes that capability, which is the capability that the display TopComponent (i.e., the Jmol viewer) is listening for, the display TopComponent is able to get hold of the published Jmol script provided by the capability and can then render it.

So "opening", below, means nothing more than publishing a capability which the display TopComponent is prepared to handle. And as soon as the capability is published, it needs to be removed again from the Node's Lookup, otherwise when you scroll up and down and get back to that same Node (i.e., you select that same Node again), it will be rendered all over again, which is not what you want in this scenario, since here the Action, and only the Action, should cause the rendering to take place.

@ActionID(category = "JMol/Leaf",
id = "org.netbeans.jmol.explorer.LoadScriptAction")
@ActionRegistration(displayName = "#CTL_LoadScriptAction")
@ActionReferences({})
@Messages("CTL_LoadScriptAction=Load Script")
public final class LoadScriptAction implements ActionListener {
    private final OpenCapability context;
    public LoadScriptAction(OpenCapability context) {
        this.context = context;
    }
   @Override
   public void actionPerformed(ActionEvent ev) {
        context.open();
    } 
}

What you end up with is a cascading set of capabilities—once the OpenCapability is retrieved from the Lookup and "open" is called, the ProteinLoadScriptCapability is added to the Lookup, at which point the loosely coupled display TopComponent receives a notification that it needs to open itself and render a script. It's really powerful and simple and results in a loosely coupled viewer/editor relationship.

Join the discussion

Comments ( 2 )
  • Joris Snellenburg Tuesday, January 10, 2012

    Very nice! I would love to check out the sources for this, but jave.net seems a bit slow in it's approval of new projects.


  • Ernest Sunday, January 15, 2012

    A small stylistic nitpick: make the second ProteinNode constructor private, as it does not need to be public, and I suspect that it invites instantiating ProteinNode with non-empty InstanceContents which may lead to exceptions.


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.