Action System Migration
Migration Guide
Before starting to migrate the Action implementation manually you can use a tool which will give you a starting point for the migration.
The class com.bc.ceres.nbmgen.NbCodeGenTool
in snap-deploy
will create a class template for each action implemented in BEAM for you. It compiles the information from the module.xml
and the implementation class into the template.
This tool can have three parameters.
- The path to the project to process
- Switch to let the tool run in dry-mode
- The base output path. The structure of the input project is retained (optional)
BEAM actions extend the class ExecCommand. This is not possible in SNAP any more. Instead you simply extend AbstractAction
or implement an ActionListener
. When you need a simple action which has no complex enablement logic you can use the ActionListener
option. For actions which shall only be enabled if a certain object is select you can put the object which needs to be selected as a parameter to the constructor.
public class MyAction extends AbstractAction { public MyAction(SelectedObject obj) { } }
If you need a more fine grained context sensitivity or multiple objects shall be selected before the action is enabled you should implement the interface ContextAwareAction
. A typical implementation would look like the following
@ActionRegistration(displayName = "Compare Bands", lazy = false) @NbBundle.Messages({"CTL_CompareBandActionName=Compare Bands"}) public class CompareBandAction extends AbstractAction implements ContextAwareAction, LookupListener { private final Lookup lkp; private final Lookup.Result<Band> result; public CompareBandAction() { this(Utilities.actionsGlobalContext()); } public CompareBandAction(Lookup lkp) { super(Bundle.CTL_CompareBandActionName()); this.lkp = lkp; result = lkp.lookupResult(Band.class); result.addLookupListener(WeakListeners.create(LookupListener.class, this, result)); setEnabled(false); } @Override public Action createContextAwareInstance(Lookup lkp) { return new CompareBandAction(lkp); } @Override public void actionPerformed(ActionEvent e) { // Do the action stuff } @Override public void resultChanged(LookupEvent le) { setEnabled(result.allInstances().size() >= 2); } }
Note
The Annotation @NbBundle.Messages only works if the package where the action is implemented is not yet defined in an other module.
Having the package org.esa.snap.rcp.actions
in snap-rcp and using the same package in a second module the Bundle key will not be found by NetBeans. Probably because it first finds the bundle property file of snap-rcp but the key for the action is not specified there.
HowTos
Defining a Group of Actions
In the Nodes API is a method available (getActions(boolean context)
) in order to define the context menu. But providing here a defined set of actions is not good because it cant be extended by plugins. Instead it is better to define a new group.
For the context menu of bands a group named 'Context/Product/Band', or speaking in the System FileSystem language a new 'Folder', has been defined. This group can either be referenced from the System FileSystem or from the ActionReference
annotation in the Java code.
... @ActionReference(path = "Context/Product/Band", position = 100) public class OpenImageViewAction extends AbstractAction { ... }
<folder name="Context"> <folder name="Product"> <folder name="Band"> <file name="org-openide-actions-CopyAction.shadow"> <attr name="originalFile" stringvalue="Actions/Edit/org-openide-actions-CopyAction.instance"/> <attr name="position" intvalue="505"/> </file> </folder> </folder> </folder>
The PNode
and PNNode
classes use the org.esa.snap.rcp.nodes.PNNodeSupport#getContextActions(...)
implementation. It loads the actions of this group, either defined in the layer.xml or by an annotation by using the utilities method Utilities.actionsForPath(String).
@Override public Action[] getActions(boolean context) { ArrayList<Action> actions = new ArrayList<>(Utilities.actionsForPath("Context/Product/Band")); return actions.toArray(new Action[actions.size()]); }
Referencing System Actions in System FileSystem
In order to define System Actions in the layer.xml it is necessary to know their ID. The easiest way to find this is to use the NetBeans IDE. Within a NetBeans module expand in the Projects Explorer the 'Important Files' folder. There you can find the XML Layer (probably only if the module has already contribution to the System FileSystem. One of the elements '<this layer>' contains only the definitions of this module but the other 'this layer in context' contains also the inherited definition. In this file you can find the IDs and other settings of the system actions. For example the cut action:
<folder name="Actions"> <folder name="Edit"> <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.core.ui.resources.Bundle"/> <file name="org-openide-actions-CutAction.instance"> <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.callback"/> <attr name="key" stringvalue="cut-to-clipboard"/> <attr name="iconBase" stringvalue="org/openide/resources/actions/cut.gif"/> <attr name="displayName" bundlevalue="org.openide.actions.Bundle#Cut"/> </file> </folder> </folder>
Retrieving a Single Action
There is a utility method for obtaining a single action by its ID. The method Action.forID(String category, String ID)
can be used for example to get the preferred action of a Node.
NewAction and NewType API
The NewAction
class, if added to e.g. the context menu of your Node, lets you add actions for creating sub-nodes.
Additionally to the NewAction
in the context menu the getNewTypes()
must be implemented. This method returns an array of NewType
implementations. Each of them defines a way of creating a new sub-node. Actually everything can be done in the implementations but they are intended for adding new sub-nodes. Because of this intention an "Add " prefix is added to a single menu item text. If there are more then one NewType
implementations they are grouped under a "Add" menu item.
For more details see Geertjan's Blog
Creating a Sub-Menu
In order to group action which belong together in a sub-menu you just need to adapt the @ActionReference annotation.
@ActionReference(path = "Menu/Tools/My Special Group", position = 100)
Each part of the path makes up a sub-menu. If it does does not already exist it is created. The action annotated with the afore mentioned annotation will be placed in a sub-menu with the label "My Special Group". The same applies for the layer.xml. If one or more actions are wrapped into a folder element a sub-menu is created.
<folder name="Menu"> <folder name="Tools"> <folder name="My Special Group"> <file name="org-openide-actions-CutAction.shadow"> <attr name="originalFile" stringvalue="Actions/Edit/org-openide-actions-CutAction.instance"/> <attr name="position" intvalue="510"/> </file> </folder> </folder> </folder>
Unfortunately this seems not to work for context menus.
The only way to add sub-menus to the context menu of a node I have found in the NetBeans Forum. However the solution described there is more than 3 years old and not very NetBeanish.
I've send a question to the mailing list and waiting for an answer.
Resources
- Class
Actions
NetBeans wiki: How do I create an Action that is automatically enabled and disabled depending on the selection?
- Geertjan's blog: How to Create an Action Annotation for NetBeans Platform Applications
- Geertjan's blog: org.openide.actions.NewAction
- DZone: Tip: How to Create Submenus on the NetBeans Platform