org.openide.actions
package. The base classes used to create new actions reside in
org.openide.util.actions
.
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.
To handle this situation,
The API provides a few standard types of context sensitivity,
although naturally it is possible to write more yourself:
(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.)
When a special presentation is required, methods such as
Note that 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).
That's it for creating the action class itself. Now another
implementation class should provide the performers for it, e.g.:
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.
CallbackSystemAction
s
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.
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.
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.
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.
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.
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.
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.
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.
// 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.
Using
CookieAction
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 ();
}
}
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 useable
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.
Some other ways that actions may be attached:
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.
TopComponent.getSystemActions()
.
For example, Editor windows provide a few right-click actions in the
Editor tab, such as Save and Close.
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.
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.
Using instances
Central to the management of menus and toolbars is the notion of
instances. An instance is just any Java object, generally
of a particular type appropriate to its use, which is provided from
some object (generally, a data object) using the
InstanceCookie
.
As an example, menu items may be added by inserting data objects
that provide this cookie into the proper folder, and having the
instance be a system action (or other things).
Where do these instances come from? Technically, it is up to you
to decide how to provide InstanceCookie
; you could if
really necessary create your own data loader that provides it
according to some unusual scheme, and add files recognized by that
loader to the right folder. Practically, the APIs provide
implementations of this cookie sufficient for normal purposes.
The most common way to provide an instance is using
InstanceDataObject
.
This is a type of data object whose sole purpose is to provide the
instance cookie. It does so based on a class name you supply.
There are two styles of instance file. The simpler is just named
according to the class for which you want an instance, with dots replaced
by dashes: com-mycom-mymodule-MyAction.instance produces
an instance of the class com.mycom.mymodule.MyAction
.
If you wish to create multiple instances of the same class in a single folder,
or if you need the data object to have a particular
name,
you can use the second style: place the class name as above in
brackets and prefix it with the desired name; e.g.
C-F6[com-mycom-mymodule-MyAction].instance will have the
object name C-F6.
The instance data object simply tries to create a new instance
of the specified class when requested to by
InstanceCookie.instanceCreate()
.
This means that the class must be public, accessible through the
proper classloader (present in the IDE core or some module), and
have a public default constructor (there is no way to specify an
alternate constructor)--these are the same restrictions used by
JavaBeans instantiation.
However, any data object which can create instances may be used. One possibility is to use *.ser files, in which case serialized objects may be used to create the instances. This has the advantage that the object may be customized before serialization. In most cases it is still preferable to create a special subclass of the desired object, with a default constructor providing the proper customizations, and then to use *.instance files to provide it.
A more interesting possibility is to define an XML DTD which
provides a rich configuration structure for your kind of objects and
to use these files to provide the instance cookies. For this to work
you will need to create an
XMLDataObject.Processor
which takes a parse of such an XML file and produces a proper
InstanceCookie
from it, then create an
XMLDataObject.Info
description of the processor and register it into the system using
XMLDataObject.registerInfo(...)
in your
ModuleInstall.restored()
method.
As an interactive demonstration of these things, first go into
Filesystem Settings and make the system file system (first in
the list) visible; then explore its contents in Filesystems,
going into some subdirectory of Menu. Note the various
actions and menu separators; these are all by default instance data
objects.
You may find some of these on disk in your installation directory
under system/ if you have customized them, but by default
they live in memory only.
You may copy-and-paste these instances from one place to another;
create new ones on disk and watch them be recognized and inserted
into the menus after a few seconds; and you may also choose
Customize Bean (which really customizes the provided instance) on
(say) a toolbar separator (under However, occasionally you may need more precise control over folder
ordering; if so you may retrieve the folder in question and use
Toolbars
) to change
the separator size, and serialize the result to a new
*.ser
file which should then create a toolbar
separator.
Working with folders of instances
There are a few things you should know about dealing with folders
containing instances that are used by the system:
Tools
folder underneath the Menu
well-known folder represents the global menu named
"Tools".
DataFolder.setOrder(DataObject[])
to fix its ordering. To use this method, it is
recommended that you first create all the files you need to in the
folder (or preferably have them be created for you by a layer); use
getChildren()
to retrieve the default ordering; and then move your own data
objects to more appropriate positions based on whatever heuristics,
leaving the order of existing
objects otherwise intact. Now you can set the new order, which will
also fix the order in menus and so on. Such code should typically
be run during
module installation.
FolderInstance
is typically used to supply instances from entire folders, by
recursively scanning the contents and collecting them together in a
meaningful fashion. You do not need to know this to install
instances into existing systems, but this class will be useful if
you wish to create your own instance-based configuration system.
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:
JMenuItem
,
which will be inserted as-is; you are responsible for its
appearance and behavior.
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.
JSeparator
(or a subclass), to separate items in the menu.
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
.
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:
Presenter.Toolbar
,
as just mentioned.
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.
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:
ToolbarPool.Configuration
(you should implement
this interface according to your needs), which will then be used as
a configuration.
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 instance 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
Node actions class diagram
Project actions class diagram
Cookie actions class diagram
Callback actions class diagram