Overview

Javadoc

Most standard system actions are to be found in the org.openide.actions package. The base classes used to create new actions reside in org.openide.util.actions.

Contents

Actions API

The Actions API handles many user interactions with the IDE, for example from toolbars and menus.

Introduction to Actions

All actions in the IDE are subclasses of SystemAction, which extends the Swing action API to interact smoothly with the IDE; it provides abilities such as context sensitivity, callback support, automatic presentation devices in menus and toolbars, and a few miscellaneous features used by the IDE.

Actions are typically either presented in popup menus, or otherwise attached to a component such as a window, node, data object, or file system.

One important thing to note is that action classes should be singletons, meaning that any instance of the class is interchangeable, and all useful state should be static. For this reason, actions are commonly specified by class name only. SystemAction.get(...) may be used to find a singleton instance based on the class.

Callbacks

In some cases it is undesirable to have the action class itself perform the implementation work. Sometimes it is necessary to change the implementation while the IDE is running. Just as likely, though, is that the action class needs to be make public in some API set (the IDE's Open APIs, or a module's exposed API), to make it possible to attach the action to components; but the implementation of the action ought not be present in a public class.

To handle this situation, CallbackSystemActions behave like a regular action, but permit an implementation of the work they perform to be attached to them from another class (typically in a hidden implementation package). This is commonly used in the Open APIs - for example, NextTabAction is a callback, because the actual class capable of manipulating the window system so as to rearrange tabs is not part of the Open API system - but it has access to the Open APIs and arranges to have NextTabAction have an ActionPerformer from the implementation code only when it is possible to advance a tab in the current window.

Context-sensitive actions

Frequently it is desirable that certain actions be automatically enabled or disabled whenever something changes in the system that affects their applicability. For example, if a node for an image is selected in the Explorer, a number of toolbar buttons (Execute, e.g.) will be grayed out. But when an applet is clicked, these will be enabled because they are now applicable. Correct use of context-sensitivity greatly reduces the chance of user error, the number of apologetic dialog boxes needed, and generally makes the application feel more responsive - so module authors should use them whenever possible.

The API provides a few standard types of context sensitivity, although naturally it is possible to write more yourself:

  1. CallbackSystemAction.setSurviveFocusChange(...) (and similar methods in other classes) may be used to cause actions to be automatically disabled whenever the active window (the window with focus, typically) changes. In and of itself this may not be so useful (since it has to be reenabled somehow anyway), but is important in conjunction with other sensitivities.
  2. NodeAction is used for actions which pertain to a set of nodes, and is thus sensitive to the current set of selected nodes - subclasses can specify whether a given set of nodes should make the action enabled or not, whenever this set changes. Most commonly the node selection is provided by an Explorer window; however, any other "top component" may also provide a node selection - for example, Editor or Form Editor windows create a node selection consisting of the selected open file. This means that e.g. a Compile action will be available whenever a compilable source is selected in an Explorer window, or is opened in an focussed editor or form.
  3. CookieAction is similar to NodeAction, except that instead of expecting the subclass to check all the nodes itself, it assumes that the sensitivity is based on the cookies provided by the selected nodes. This may be the most useful type of action, in fact, because it allows a great deal of flexibility - any new nodes may just provide the correct cookie or cookies, and the cookie action will automatically handle them correctly. Typically, cookie actions are of general utility across the IDE and many of them are displayed prominently on the system toolbar.

    (A callback action may also provide very similar functionality, of course - the implementation class just uses an event listener to keep track of interesting changes in the system, and attaches or detaches the callback as needed. Cookie actions just automate the listening.)

Presenters

System actions in general are designed to be presented to the user using some GUI devices. The action system is designed to make this especially easy and abstract, so that module authors need not manually configure components for every action, providing all the event wiring themselves. Commonly, it suffices to give the action a name and an icon, leaving the rest to standard presenters.

When a special presentation is required, methods such as CallableSystemAction.getToolbarPresenter() are called to provide a presenter such as Presenter.Toolbar, which is used to create the appropriate UI element. A few actions provide special implementations of the UI elements where appropriate.

Note that JMenu is also a JMenuItem, so you may create submenus by providing a system action that does not perform any action itself, but which has a special menu presenter creating a submenu. This is the usual idiom for creating submenus. Typically, you will actually have all of the submenu's items be based on system actions which themselves have a menu presenter (usually the default one); then you need only collect all of the resulting menu items together in a menu, which means that little work needs to be done to create the submenu. The top-level action providing the submenu as its presenter will generally be a direct subclass of SystemAction, and the SystemAction.actionPerformed(...) method should have an empty implementation. Both the menu presenter and popup presenter are typically specified explicitly for such an action (and should request the menu and popup presenters, respectively, of the sub-actions).

Creating Actions

Creating an action is generally not very complex.

Subclassing the right base class

Since there are a number of different available superclasses to choose from, two example base classes will be used.

CallbackSystemAction

CallbackSystemAction may be subclassed by first deciding what, if any, state the action needs; if it does need some, this should be stored in the class itself, rather than in the instance, as it should be a singleton. Conventionally, SystemAction.getValue(...) and SystemAction.putValue(...) are used for storage.

SystemAction.getName(), SystemAction.getHelpCtx(), and SystemAction.iconResource() should all be overridden to provide basic information about how to display the action in its presenters.

Note that you may include an ampersand in the name before a letter to indicate a desired mnemonic position. E.g. My Act&ion should use the i as a mnemonic. The default menu presenter honors these mnemonics; the default popup presenter does not (intentionally).

SystemAction.initialize() might be overridden if needed (call the super method first); typically this is unnecessary. The initialize method will be called the first time the action is used, so this is a reasonable place to perform setup for the action, rather than static initializers. Note that it should not be called in the constructor (actions should generally not have constructors anyway) and that it should initialize class state, not instance state (which there should generally be none of). Also note that any state which may need to be associated with the action should be stored using SharedClassObject.getProperty(key) and SharedClassObject.putProperty(key, value, true).

CallbackSystemAction.setSurviveFocusChange(...) might be called in the initialize method if it needs to be changed.

That's it for creating the action class itself. Now another implementation class should provide the performers for it, e.g.:

// Get the action:
MyCallbackAction action = (MyCallbackAction) SystemAction.get (MyCallbackAction.class);
// Some subsystem, changes in which should affect the action:
FooSystem fooSys;
// Attach a listener for the action's benefit:
fooSys.addWidgetListener (new WidgetListener () {
    public void widgetAdded (final WidgetEvent ev) {
        // Enable it, and tell it what to act on.
        action.setActionPerformer (new ActionPerformer () {
            public void performAction (SystemAction ignore) {
                ev.getWidget ().doMyActionStuff ();
            }
        });
    }
    public void widgetRemoved (WidgetEvent ev) {
        // Now disable it.
        action.setActionPerformer (null);
    }
});
Note that the implementation may freely use package-private calls and so on, while the action itself is easily separable, and may be publically used to attach to nodes and so on.

CookieAction

Using CookieAction is fairly easy, as it assumes that the objects providing the cookies have already done most of the hard work in providing cookie supports and determining which objects should contain the cookies. Basic implementation of the action is similar to that for CallbackSystemAction, but now a few more methods should be implemented, for example:
public class MyScanAction extends CookieAction {
    // help context, display name, icon...
    public String getName () {
        return "Scan Things";
    }
    public HelpCtx getHelpCtx () {
        // Update with real help when ready:
        return HelpCtx.DEFAULT_HELP;
    }
    public Class[] cookieClasses () {
        // Which cookies is this action sensitive to?
        return new Class[] { MyScanCookie.class };
    }
    public int mode () {
        // At least some of the selected nodes must have this cookie.
        return MODE_ANY;
    }
    public void performAction (Node[] selectedNodes) {
        MyScanContext ctxt = new MyScanContext ();
        for (int i = 0; i < selectedNodes.length; i++) {
            MyScanCookie cookie = (MyScanCookie) selectedNodes[i].getCookie (MyScanCookie.class);
            if (cookie != null)
                ctxt.addScannable (cookie);
        }
        ctxt.scanAway ();
    }
}

Your performAction method can freely take as much time as it wants and can even block on other threads. But if you need to use the GUI, be sure to guard yourself with (for example) SwingUtilities.

Now this action may be installed onto a toolbar, for example, and it will be enabled automatically whenever the node selection includes at least one "scannable" object.

Installation

Global installation of an action may be accomplished using the Modules API, if you want it to appear as a so-called "service action", e.g. under the Tools popup menu on many nodes.

Global installation is mostly appropriate for actions, such as cookie actions, which are carefully designed to be sensitive to specific conditions, and are appropriate for constant visibility. Some actions only make much sense in a very local context, attached to a particular object; these should not be globally installed in general.

It is also possible to install actions globally into menus and toolbars, though not using the manifest; typically this would be done using an installation layer. For details, see below.

It is possible that you would not need any of these global installation options, if you did not want your action to be installed using any of these methods. For example, the action might be available only on a popup menu, in which case the details of when to present it would be handled by some node or data loader. See below.

Using Standard Actions

Many of the commonly-used actions available in the IDE (used either by the core implementation of the IDE, or a common module) are located in the org.openide.actions package. As a general rule, actions such as these are not really appropriate for direct programmatic use (i.e. subclassing); rather, they are provided in the API so that they may be attached to various types of objects created by module authors.

Attaching to an existing component

An action that does not have global scope and sensitivity should usually be explicitly attached to components that can use it. For example, the call DataLoader.setActions(...) may be used to provide context-menu actions appropriate to all data objects created by that loader, e.g.:
public class MyDataLoader extends DataLoader {
    public MyDataLoader () {
        super (MyDataObject.class);
        // use initialize() for all else!
    }
    protected void initialize () {
        // other initialization
        setActions (new SystemAction[] {
            SystemAction.get (OpenAction.class),
            SystemAction.get (FileSystemAction.class),
            null,
            SystemAction.get (CutAction.class),
            SystemAction.get (CopyAction.class),
            SystemAction.get (PasteAction.class),
            null,
            SystemAction.get (DeleteAction.class),
            SystemAction.get (RenameAction.class),
            null,
            SystemAction.get (SaveAsTemplateAction.class),
            null,
            SystemAction.get (ToolsAction.class),
            SystemAction.get (PropertiesAction.class)
        });
    }
}
This set of actions will then provide the context menu for the data objects belonging to this loader. It is the responsibility of the loader author in this case to make sure that all of the provided actions make sense for the object, and will run the correct code - for example, enabling the Open action means that the object will have to provide a usable OpenCookie.

Note that modules may, if necessary, change the set of actions associated with a data loader, by calling DataLoader.getActions(), carefully modifying the resulting list (by means of scanning for well-known places in the list, handling unexpected configurations, looking for null separators, and inserting the desired actions as needed), and then setting this new list back with setActions. This option should be reserved for situations in which using a service action is not reasonable from a UI perspective; the module should add the actions at module install/restore time, and remove them again at uninstall time. This technique will probably be deprecated in favor of the new Looks API. For now please avoid it.

Some other ways that actions may be attached:

  1. Nodes may attach actions by overriding AbstractNode.createActions(), or by calling AbstractNode.setDefaultAction(...). Nodes may even have more specific uses of actions, for example for display in an explored "context" rather than on the node itself.
  2. Top components (dockable window-like frames) may attach actions using TopComponent.getSystemActions(). For example, Editor windows provide a few right-click actions in the Editor tab, such as Save and Close.
  3. FileSystem.getActions() provides actions attached by default to all files in the filesystem - commonly used for things like refreshing from disk, providing version-control support, etc.
  4. Custom editors may automatically expose some actions to the system, as described in the Editor API.

Advanced Action Installation and Maintenance

Actions and their associated UI apparatus (menu items, toolbar buttons, even whole toolbars and clusters of these) can be installed and configured by modules in a flexible fashion, according to the level of sophistication required by the module. (If no special display system is needed, module authors will wish to use the simpler and quicker systems involving attaching actions to a specific object, or creating a tools action.)

In all of these cases, a module should use an installation layer to handle the installation and uninstallation of these UI elements. Pay particular attention to making sure that your installation technique will interact well with other unknown modules that may also have installed actions. In very complex situations ModuleInstall can be used to fully customize the installation and uninstallation, but typically layers will suffice and be easier to use.

For all of these installation mechanisms, you will need a basic understanding of some things such as instances which are described at length in the Services API: Working with Instances and Folders of Instances. Installation of actions follows these general principles, so the remainder of this section discusses the particular places where actions may be installed.

Menu installation

The class MenuBar is used by the IDE to construct the menu bar for the Main Window. By default it looks for the proper contents of these menus in the folder given by Places.Folders.menus(), corresponding to the Menu folder in the system file system. To add menu items to the Main Window, for example:
<filesystem>
    <folder name="Menu">
        <folder name="Build">
            <!-- After Set Arguments... -->
            <attr name="org-netbeans-core-actions-SetArgumentsAction.instance/com-mycom-mymodule-MyAction.instance"
                  boolvalue="true"/>
            <file name="com-mycom-mymodule-MyAction.instance"/>
            <!-- ...and before Execute. -->
            <attr name="com-mycom-mymodule-MyAction.instance/org-openide-actions-ExecuteAction.instance"
                  boolvalue="true"/>
        </folder>
    </folder>
</filesystem>

Menus under the menu bar are typically represented by folders, whose name gives the name of the menu and whose contents gives the items. (You could also, if you wished, provide an object whose instance was of type JMenu, and provide its contents using some special means.)

Within a subfolder representing a menu, there are three kinds of instances which you may provide to create items in the menu:

  1. An actual JMenuItem, which will be inserted as-is; you are responsible for its appearance and behavior.
  2. A system action which implements Presenter.Menu (as most system action subclasses do by default). In this case, the returned JMenuItem will be used in the menu. Also note that bookmarks (*.url files) as created by the User Utilities module provide a cookie implementing this interface and so are useful things to place in menus.
  3. An instance of JSeparator (or a subclass), to separate items in the menu.
  4. If instead of an InstanceCookie-providing data object, you add one which provides ExecCookie (e.g. a Java source file with a main method), then this object will be used - the action performed by the menu item will simply be to execute the object. This option is the quickest way to add functionality to a menu, though it is not as configurable as using a SystemAction.

Toolbar installation

Installing items into toolbars can be done in a similar fashion to that available for menu installation, but with more options for customized behavior.

The normal way to customize the Main Window's toolbars is to add an item or two to one of the existing toolbars in the Main Window. For example, the Data toolbar (normally presenting a Find action and several others) is a good place to try. To add a button to it, you must have a system action with a toolbar presenter (which most do by default); simply add an InstanceDataObject providing this action to the Data subfolder of Places.Folders.toolbars() which on the system file system is the folder Toolbars/, ensure the folder ordering is correct, and you should have a button for your action appear on that toolbar (remember to specify an icon for the action!).

The items in such a toolbar may actually be derived from several sorts of instances:

  1. A system action implementing Presenter.Toolbar, as just mentioned.
  2. Any Component. Usually this will just be a JToolBar.Separator, since special widgets such as combo boxes and so on are better given as the toolbar presenter of an action.

    Note that you can customize the size of separators and use either *.ser files or custom subclasses of JToolBar.Separator with InstanceDataObject to install the customized separator.

  3. As with menu items, any data object with an ExecCookie.

The whole toolbar as created when a folder is encountered is actually a Toolbar, which is a special subclass of JToolBar created for the purpose of assembling components from folders in this fashion. (You might want to use this class to add a toolbar based on the standard assembly mechanism to other windows of your creation.) If you want to replace the whole toolbar with a special component, you may do so - sophisticated modules like the Form Editor do this to present special-format toolbars such as the Component Palette. You need only provide an instance of some subclass of Component (in the main toolbars folder) rather than a subfolder.

All available toolbars, whether created by the normal folder-scanning mechanism or whether custom written, are available using ToolbarPool.getToolbars(). However, at any given time not all of these are visible, and the visible toolbars may be in different relative positions, including arranged in rows. All of this information is controlled by a ToolbarPool.Configuration object. All available configurations are listed in ToolbarPool.getConfigurations(), and ToolbarPool also permits the current configuration to be retrieved and set. Normally the choice of configuration is made by the user in a popup menu on the Main Window. Users may also configure toolbars on a per-workspace basis, though currently this is not accessible from the APIs.

What are these configurations and how may new ones be added? Essentially, a configuration is just a component which displays the toolbars it represents (it is the responsibility of the configuration to determine which these are), and also provides a popup menu allowing the user to switch to a different one. To add a new configuration, you should as usual add an instance to the main toolbars folder, which should be a subclass of either:

  1. ToolbarPool.Configuration (you should implement this interface according to your needs), which will then be used as a configuration.
  2. Component (but not JToolBar), in which case the supplied component will be wrapped in an adapter which provides the name and a standard popup menu, while the display is otherwise handled by the component.

Currently, the standard toolbar configurations are a private implementation of ToolbarPool.Configuration which reads the configuration based on an XML file. The format of this file is not specified by the Open APIs, so modules should not attempt to modify it. (A user-level customizer for such files may be supplied.) Rather, module authors should note that the standard implementation lists toolbars from the pool which should be displayed, and possibly also toolbars which should not be displayed; any toolbar in the pool not explicitly mentioned will just be displayed somewhere at the end of the component. So, module-supplied toolbars will at least appear, though their exact placing will not be customizable.

Actions pool

The folder obtained by Places.Folders.actions() (folder Actions/) contains a list of standard actions, as instances in the same style as is used for menus and toolbars. The contents of this pool are not used directly by the IDE, other than appearing under Session Settings; rather, it exists so that users can safely customize menus and toolbars (including removing items) and still have a pool available that they can restore actions from later, if they change their minds. So modules which install a global menu or toolbar action should generally also install this action into the actions pool, to give users the chance to customize the menus and toolbars freely.

Installing keyboard shortcuts

The best way to install keyboard shortcuts is to make an instance of the action in question, and place it in the Shortcuts/ folder. The data object name will give the keyboard sequence, named according to the method Utilities.keyToString(KeyStroke). Such instances will be used to create the global keymap.

UML Diagrams

Diagrams are divided into several sections, each shows specific kind of actions. Basic actions diagram shows action structure in general.

General actions class diagram

General actions UML

Node actions class diagram

Node actions UML

Project actions class diagram

Project actions UML

Cookie actions class diagram

Cookie actions UML

Callback actions class diagram

Callback actions UML

Built on December 12 2001.  |  Portions Copyright 1997-2001 Sun Microsystems, Inc. All rights reserved.