In part 1, we created an Action for files of a certain type in the NetBeans Platform. What was special was that the Action is contextually sensitive to a custom defined "OpenCapability" interface. If an implementation of that interface is present, something can happen. If it is absent, something different can happen.
But what is it that can happen? I.e., how can the above infrastructure be used to do something useful? In this second part, we continue by looking at a scenario where an Action is enabled/disabled based on the presence/absence of a key found in a file. If the key is present, the value is used to find a specific TopComponent to be opened when the Action is invoked. As with the earlier part, this uses an approach introduced by Toni Epple in the recent NetBeans Platform Certified Training to the Institute of Marine Research in Bergen, Norway.
  1. In my module's layer file, I have a folder named "Reference", containing two FileObjects. The definition of the FileObjects points to actually existing files within my module:
  2. So, there are two files within my module, named "cruises.imrc" and "logbooks.imrc". The former contains this content: window=CruisesTopComponent
    ... while the latter, i.e., "logbooks.imrc" is empty.
  3. Next, I enable the NetBeans Platform to recognize the above two files. I do this by using the New File Type wizard to create support for files that have "imrc" as their file extension. The wizard registers the new file type and also creates a skeleton DataObject for it.
  4. Within that generated DataObject, I load files for which the DataObject is created. I load them as Properties files. Then I check for the existence of a "window" key in that file. If that key exists, I add the "OpenCapability" to the cookieset. (I agree with Jean-Marc Borer in my recent blog entry relating to this topic, i.e., that Lookup should be used rather than Cookies. However, here I'm simply going along with the code generated by the New File Type wizard, i.e., code relating to cookies is generated, hence my additional code uses that too.) public class IMRCategoryDataObject extends MultiDataObject { Properties p; public IMRCategoryDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException { super(pf, loader); p = new Properties(); //"pf" is the FileObject that is bound to this DataObject in the layer file, //thanks to the New File Type wizard. It has an InputStream, so that we //can load it into a Properties object: p.load(pf.getInputStream()); CookieSet cookies = getCookieSet(); cookies.add((Node.Cookie) DataEditorSupport.create(this, getPrimaryEntry(), cookies)); OpenCapability oc = new OpenCapability() { @Override public void open() { TopComponent tc = WindowManager.getDefault().findTopComponent(p.getP roperty("window")); tc.open(); tc.requestActive(); } }; if (p.getProperty("window") != null) { getCookieSet().assign(OpenCapability.class, oc); } } @Override protected Node createNodeDelegate() { final DataNode node = new DataNode(this, Children.LEAF, getLookup()); node.setDisplayName(node.getName()); return node; } @Override public Lookup getLookup() { return getCookieSet().getLookup(); } }
    Note: The rest of this blog entry discusses the highlighted code above in detail. (The remainder of the code above is not interesting, i.e., it is standard DataObject code.)
  5. Then I create Nodes on top of the two FileObjects and display those Nodes in an explorer view. Only the Node for the "cruises.imrc" has an enabled "Open Window" action, since the Node for the other FileObject has no "window" key. In other words, the "OpenCapability" is only available in one of the two cases, causing the Action registration to enable the menu item for "cruises.imrc", but not for "logbooks.imrc".
    As in the previous blog entry, the "OpenCapability" is defined as follows: package no.imr.viewer; public interface OpenCapability { public void open(); }
    ...while the NetBeans Platform is sensitive to the above capability via this registration in the layer file, which is linked into the contextual menu of the IMRCNode displayed in the explorer view via a layer entry in Loader/text/x-imrcategory, which is the MIME type of files using the ".imrc" file extension:
    Since the "OpenCapability" is only added to the cookieset if the "window" key is present, which is absent in the case of "logbooks.imrc", the Action is disabled when you right-click the Node for "logbooks.imrc". When the Action is enabled, invocation of the Action results in "open" being called on the "OpenCapability": public final class OpenCategoryAction implements ActionListener { private final OpenCapability context; public OpenCategoryAction(OpenCapability context) { this.context = context; } public void actionPerformed(ActionEvent ev) { context.open(); } }
    And what does "open" mean in this context? In this context, defined in the DataObject with which this blog entry started, "open" results in the opening of the TopComponent for which the "window" key has been defined. In the case of the "cruises.imrc" file, that means the "CruisesTopComponent".

And that's how setting a property in a file can result in an Action being enabled/disabled on a Node. Why is this useful? Well, maybe a new module will register a new ".imrc" file within the "Reference" folder. Let's say, for example, that a "Fish" node should be displayed under the "Reference" node. But, possibly that "Fish" node will not have a TopComponent to be opened. In that case, the ".imrc" file will not have a "window" key, so that the "Open" menu item will be disabled. If, on the other hand, the "Fish" node has a related TopComponent, i.e., one displaying the currently selected fish, the "fish.imrc" file would have a key/value pair like this, for enabling the "Open" menu item and causing the "FishTopComponent" to be found: window=FishTopComponent
And that's how new additions to the "Reference" node can come from the outside, while still having distinct behavior. In the case of the Institute of Marine Research this could be pretty useful since each Node represents a different database which could have some very specific functionality. In such scenarios, contextually available (enabled/disabled present/absent) Actions on Nodes is essential and the above approach very elegantly solves this requirement. It is also a generic approach that can be used in any number of similar scenarios.
Best thing to do, however, is to make sure that the TopComponent, ".imrc" file, and layer registration are all found within the same module. That's what would probably be done anyway, since the usecase for this scenario assumes that each file would come from a different module, but it's important to remember anyway. For example, imagine that the TopComponent comes from one module, while the ".imrc" file comes from another one. Who is to say that both modules will be present and that the TopComponent will actually be found? Only way to really guarantee this is to put everything related to specific instance of this infrastructure into the same module.
Of course, the context-aware opening of TopComponents is just one example. The sky is the limit when you're using the NetBeans Platform, but if you've read this far, you probably already know that.


Read More about [Creating Context-Sensitive Capabilities for File-Based Nodes (Part 2)...