Creating Context-Sensitive Capabilities for File-Based Nodes (Part 1)

Here's a capability for opening something:
package no.imr.viewer;

public interface OpenCapability {
    public void open();
}

In my DataObject, I assign the OpenCapability to the dynamic Lookup of the DataObject, take note of the code in bold below:

public class IMRCategoryDataObject extends MultiDataObject {

    InstanceContent content = new InstanceContent();

    public IMRCategoryDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException {
        super(pf, loader);
        CookieSet cookies = getCookieSet();
        cookies.add((Node.Cookie) DataEditorSupport.create(this, getPrimaryEntry(), cookies));
    }

    @Override
    protected Node createNodeDelegate() {

        final DataNode node = new DataNode(this, Children.LEAF, 
           new ProxyLookup(getLookup(), new AbstractLookup(content)));

        content.add(new OpenCapability() {
            @Override
            public void open() {
                //Define what should happen for each node:
                StatusDisplayer.getDefault().setStatusText("Opened: " + node.getName());
            }
        });

        return node;

    }

    @Override
    public Lookup getLookup() {
        return getCookieSet().getLookup();
    }
    
}

(Alternatively, use "getCookieSet.assign(OpenCapability.class", oc), if you want to use cookies, which is simply an outdated pattern replaced by Lookup.)

I then use the New Action wizard to create a new "OpenCategoryAction" action that is sensitive to "OpenCapability" objects (which is added to the dynamic content of the DataObject, as shown above). Here's the layer entry generated:

<folder name="Actions">
    <folder name="Edit">
        <file name="no-imr-viewer-OpenCategoryAction.instance">
            <attr name="delegate" methodvalue="org.openide.awt.Actions.inject"/>
            <attr name="displayName" bundlevalue="no.imr.viewer.Bundle#CTL_OpenCategoryAction"/>
            <attr name="injectable" stringvalue="no.imr.viewer.OpenCategoryAction"/>
            <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
            <attr name="noIconInMenu" boolvalue="false"/>
            <attr name="selectionType" stringvalue="EXACTLY_ONE"/>
            <attr name="type" stringvalue="no.imr.viewer.OpenCapability"/>
        </file>
    </folder>
</folder>

It is registered for the MIME type of the node, which I created via the New File Type dialog, so will appear in the right-click contextual menu item of my node, having the same appearance for each node, but with behavior that depends on the particular node in question (thanks to the dynamicity of the Lookup used in the definition of the DataObject, as shown above). Here's how the ActionListener is registered (by the wizard automatically) in the layer:

<folder name="Loaders">
    <folder name="text">
        <folder name="x-imrcategory">
            <folder name="Actions">
                <file name="no-imr-viewer-OpenCategoryAction.shadow">
                    <attr name="originalFile" stringvalue="Actions/Edit/no-imr-viewer-OpenCategoryAction.instance"/>
                    <attr name="position" intvalue="0"/>
                </file>
                ...
                ...
                ...

And here's how the ActionListener is defined, simply getting the capability injected into it (thanks to the layer registration for the Action), from where the method is called, which behaves context sensitively, i.e., as defined in the DataObject above:

public final class OpenCategoryAction implements ActionListener {

    private final OpenCapability context;

    public OpenCategoryAction(OpenCapability context) {
        this.context = context;
    }

    public void actionPerformed(ActionEvent ev) {
        context.open();
    }
    
}

That's how a capability can be added to a Node, letting me dynamically compose the capabilities of my Node as and when needed. Currently, the "OpenCapability" is set in the definition of the DataObject, but since the InstanceContent is in the Lookup of the DataObject, we can dynamically add/remove the OpenCapability to/from the Node's Lookup as needed.

Comments:

Geertjan, node's lookup is different than DataObject's lookup: in the way you described OpenCapability instance is contained only in the node's lookup. Instead using getCookieSet.assign(OpenCapability.class, oc) will place oc in the DataObject's lookup.

Ciao!

Posted by Matteo Di Giovinazzo on April 14, 2010 at 10:44 PM PDT #

So, got trapped in norway or did you get out before the air was filled with ash? :-)

Posted by Christian on April 15, 2010 at 07:10 PM PDT #

Geertjan,

I would suggest to only use Lookup completely get rid off CookieSet in DataObjects. The benefit of this is that you no longer have both a Lookup and CookieSet in parallel, but you have only a single Lookup:

public class MyDataObject extends MultiDataObject {

private final InstanceContent content = new InstanceContent();
private Lookup lookup;

public AbnfDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException {
super(pf, loader);
lookup = new ProxyLookup(getCookieSet().getLookup(), new AbstractLookup(content));
content.add((Node.Cookie) DataEditorSupport.create(this, getPrimaryEntry(), super.getCookieSet()));
}

@Override
protected Node createNodeDelegate() {
return new DataNode(this, Children.LEAF, getLookup());
}

@Override
public Lookup getLookup() {
return lookup;
}

@Override
public <T extends Cookie> T getCookie(Class<T> type) {
return lookup.lookup(type);
}

@Override
protected <T extends Cookie> T getCookie(DataShadow shadow, Class<T> clazz) {
return getCookie(clazz);
}

...
}

Posted by Jean-Marc Borer on April 15, 2010 at 07:18 PM PDT #

[Trackback] Geertjan hat in seinem Blog einem kurzen Artikel darüber geschrieben, wie man eine sogenannte &#8220;Capability&#8221; dynamisch in ein eigenes Node einbaut. Er nutzt dazu die Klasse InstanceContent über die dynamisch ein Lookup verändert werden kann. ...

Posted by Java Getriebe on April 15, 2010 at 07:49 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today