|
This module provides support for creation of actions, supports and takes care of their presentation in global menus, toolbars and popup menu. By using methods and other APIs from this module one shall be able to easily create instances of javax.swing.Action which are ready to use in the NetBeans platform.
Question (arch-overall): Describe the overall architecture. Answer:The architecture of this module builds on existing NetBeans Platform abstractions that has been proven to be useful. This lowers the amount of changes in comparition from NetBeans 5.0 and concentrates them into relatively small, isolated area. The basic building block is Utilities.actionGlobalContext() and ContextAwareAction interface. These were introduced to solve focus problems when creating pop up menus on window managers with other then click-to-focus behaviours and seem to work quite well.
One of the most important goals is to remove the need to work with historic SystemAction and subclasses. Beyond the fact the they are instances of SharedClassObject and such they have limited, complex and not very useful lifecycle management, they also rely on old and currently already obsolete concepts like Node.Cookie. By using this module one does not need to care about the SystemAction at all. A knowledge and understanding of Node.Cookie is required no more as well.
Part of overall description is also list of things that are not addressed by the effort. So it is wise to state that the actions module does not addresses following issues:
The new design of actions framework is following the Swing JDK 1.3 design as close as possible. That is why it is going to follow the InputMap and ActionMap separation.
The base constraint for this is to separate definition of action key from the actual definition of an action. The key is then going to be put into a global InputMap and assigned to a KeyStroke, while the actual action to be invoked is then put into an active ActionMap and assigned to the specified key.
To achieve such state and also handle backward compatibility with older actions definition, all actions created by this module support the notion of a key and communicate with the whole system on registration of the key and default action implementation into global InputMap and ActionMap.
The basic pattern is based on layer definition inside the Actions/
folder and its subfolders in layer where one can creates an action
instance using one of the factories methods and assigns it a key:
<file name="your-module-prefix-YourActionId.instance" > <attr name="instanceCreate" methodvalue="org.netbeans.spi.actions.Factory.oneOfFactoryMethods" /> <attr name="key" stringvalue="your.module.Key" > <attr name="defaultCreate" newvalue="your.module.prefix.YourAction" > </file>
The above snippet creates a registration of an action that can
be then refered from Menu
, Toolbar
, Shortcuts
folders and by default delegates to an active
ActionMap to find out
the right action delegate to perform the operation - the value of the
key
attribute is used to ask the
ActionMap for the action delegate.
Beyond this generic delegator
the usual action definition
also includes voluntary definition of a default implementation. E.g.
some action that gets invoked if nobody provides a better implementation
and registers it to the key into its own ActionMap.
Such default implementation is taken from a value of
defaultCreate
attribute and can be instance of
existing class or boolvalue="true"
to take
the default provided by the oneOfFactoryMethods
as discussed in next usecases. This default implementation
is put into global ActionMap
which is by default parent map of any
TopComponent.getActionMap()
and as such serves as a fallback.
By doing this we keep compatibility with registrations of the old actions (they just do not delegate by default) and also ensure that all new actions are by default delegating ones. Also a way to suply a default implementation is there thus combining the flexibility of each TopComponent providing own implementation with the comfort of providing a default action suffient for common components.
One can create a context-less, always enabled action. Of course,
one can use javax.swing.AbstractAction
to do that, however the support provided by this module allows one
important addition - the action can be registered and displayed
without loading any of the classes into memory. This is helpful
for actions like Open File
, Open Project
, most of actions
in Window
submenu that opens singleton components and etc.
To register such action in a layer file use following syntax:
<file name="your-module-prefix-YourActionId.instance" > <attr name="instanceCreate" methodvalue="org.netbeans.spi.actions.Factory.delegate" /> <attr name="delegate" newvalue="your.module.Listener" > <attr name="defaultCreate" boolvalue="true" > </file>
And write a class your.module.Listener
that extends
javax.swing.ActionListener
and implements its
actionPeformed
method.
Such action is going to be always enabled and when invoked the registered
listener is going to be instantiated and its method called. The localized
name and icon of the action is going to be taken from the localized
name and icon for the file using the standard I18N mechanism for
layers - e.g. SystemFileSystem.localizingBundle
,
SystemFileSystem.icon
, SystemFileSystem.icon32
attributes. SystemFileSystem.icon24
- As actions need also 24x24 color icon, there is additional
support for specifying it. Just use
<attr name="SystemFileSystem.icon24" urlvalue="..." /> in the
layer to provide definition of 24x24 icon for the action.
If one needs a placeholder action - e.g. an action that has the
name, icon, shortcut, but does not have the actual implementation.
One can just use the standard way for registering actions in the
layer, just do not provide the defaultCreate
attribute.
The proper delegate is then going to be discovered in runtime.
It is provided by the actual selected element (usually
TopComponent).
Also the enabled/disabled state is governed by the selected element's
advices.
There are two steps one needs to do to declare such action. First of all one needs to provide a key and register such action in a layer:
<file name="your-module-prefix-YourActionId.instance" > <attr name="instanceCreate" methodvalue="org.netbeans.spi.actions.Factory.callback" /> <attr name="key" stringvalue="your.module.Key" > </file>
The localization and icon is taken from the regular layer attributes.
The value of the key
then becomes the API that others can
use to provide implementation of the action. So as a step two, one
writes its own implementation of plain javax.swing.Action
and registers it into the selected
ActionMap - the
selected action map usually is
TopComponent.getActionMap()
of the component one is writing the action for.
The callback action is then going to listen on the changes in selected elements and update it state according state of the action one wants to delegate to.
There is a FindAction situation
: the action is in fact
callback one and for example output window and editor can register
its own action delegate into their component
ActionMap.
Then all the operation are delegated to the provided delegate.
However
there is also a default
implementation based on presence
of SearchInfo
interface in the context. Project, Files, Favorites views shall
just place an instances of
SearchInfo
into their context (usually into context of some of their nodes)
and then the default impl shall extract all the needed info from there
and act accordigly. Btw.
In the old system this does not work. The provider of the default
implementation listen of set of components and if it likes them, it
adds its delegate into their ActionMap.
Ugly, is it not?
The solution to this problem could be in allowing a default
fallback action to be specified for any callback. So there
would be a factory method that would not only took a key to find
the right delegate in the map, but also a implementation of an
action to fallback to. In the declarative layer definition
it would be expressed as:
<file name="your-module-prefix-YourActionId.instance" > <attr name="instanceCreate" methodvalue="org.netbeans.spi.actions.Factory.callback" /> <attr name="key" stringvalue="your.module.Key" > <attr name="defaultCreate" newvalue="your.module.prefix.FallbackAction" > </file>
The fallback instance would then be instantiated in a lazy fashion, only if needed - e.g. if no delegate could be found in active context.
All actions are by default executed on AWT event thread. Also their enablement state is guaranteed to be computed on AWT thread as well.
If an enablement state should take longer to compute, then it is adviced to keep the action always enabled and if it is invoked in an inapropriate situation then just do nothing.
If an execution of the action's performer it taking long time, then it is adviced to move the execution to background. User shall be notified about running background tasks by the use of the ProgressAPI.
To support testing and UI scripting a contract to invoke background
actions synchronously shall be followed. If the actionPerformed
method is called with
ActionEvent(somone, id, "waitFinished")
command waitFinished
then the actionPerformed method
should return after whole initialization is over.
There is a support to make writing the above described contracts simpler. A factory method that takes an interface with three methods:
public interface AsynchronousWorker<T> { public ProgressHandle initialize(); public T work(ProgressHandle); public void finish(ProgressHandle, T); }
initialize
is called on AWT thread and returns a ProgressHandle
,
then work
is called outside of the thread and gets handle as an argument.
The finish
is again called on AWT thread and gets handle as well
as returned value from the background method. The action created
by this factory method honors the waitFinished
contract.
To support situation where one module does have a piece of data and wants other module to create an unlimited range of actions that works on such data, NetBeans provide a powerful mechanism of context aware actions.
A module who wishes to expose a piece of data creates an interface in its API to encapsulate them and makes sure the interface get's exposed in Utilities.actionGlobalContext() . This can be done by putting the interface into lookup of module's top components or lookup of its Nodes.
The other modules then just use the context
factory method
with provided Class<DataType>
of the API interface that encapsulates
the data and provide subclass of ContextActionPerformer<DataType>
that provides implementation of
actionPerformed(ActionEvent ev, DataType[] arr)
handling the execution of the action on given instances of DataType.
By using just the ContextActionPerformer
one creates action
that is always enabled if given DataType
is present in the
active context, that however may not be enough for all situations.
For example project actions needs to extract the project from a
file/data object and then decide. For them there is a way to also supply
ContextActionController<DataType>
that has method
enabled(DataType[] types)
to compute the enabled state
programmatically and control both the enablement and well as execution.
Context aware actions can be registered in a layer of module to prevent
the actual ContextActionPerformer
to be instantiated until
really needed:
<file name="your-module-prefix-YourActionId.instance" > <attr name="instanceCreate" methodvalue="org.netbeans.spi.actions.Factory.context" /> <attr name="key" stringvalue="your.module.key.for.those.who.want.to.provide.their.own.impl" /> <attr name="defaultCreate" boolvalue="true" /> <attr name="type" stringvalue="your.module.DataType" > <attr name="actionPerformer" newvalue="your.module.ContextActionPerformerImpl" > </file>
As usually, display name and icon are taken by the standard attributes provided by SystemFileSystem.
In some situations the display name of the action in menu or popup menu should change according the actual selection. This is supported for those actions where it makes sence - the callback and context actions.
When creating a callback action one can specify that the name shall be taken from the actual delegate. As a result either default name is used when there is noone to delegate to, or the name of the delegate is shown in menu and popup menu.
If one wishes to influence the display name of an context action, one needs to
write additional interface ContextActionName
that's only
method takes an array of DataType
and returns string to
be used for display name. It is possible to return different
name for popup menu and for top menu.
Another problem related to display name is that currently actions'
display name can always be influenced by associated context. This
can cause problems to infrastructure like Tools/Options dialog where
actions have name of lastly selected node. This is solved by
using Lookup.EMPTY
by default as the context for the
newly created actions. Menus, Toolbars, will then explicitly bind
these actions to Utilities.actionsGlobalContext()
which
is the context representing the main window.
In some situations an action may need to work on a different lookup
content then one provided. For example an action may need Project
interface in its context, but the original context just contains
a FileObject
.
To achieve this one needs to write a lookup to lookup convertor and use a factory methods that converts one ContextAwareAction to another. The framework then takes care of caching and the created lookup and always passing the old action the lookup transfered by the convertor.
That way the convertor can always check the content of the old
context for FileObject
and based on using some
magic query create a wrapper lookup adding also an instance of
Project
there.
This is really old issue (#9679) and it is still open as it
seemed unsolvable till now. However recently it has been found
that there is a way for the action to found out if it is the
default action or not, if it uses the Node.getPreferredAction()
method. If the context the actions works in contains just one
node and someone is building a popup menu for it, then the one
action returned from Node.getPreferredAction()
can
draw itself in bold. So this can work for
NodeAction
and
CookieAction
for the other we need some kind of node awarence
.
Anyway the default action often depends on the view
that invokes the action, so the infrastructure just provides
new factory method Utilities.actionsToPopup(actions, context, defaultAction)
and the views will be allowed to extract the default action
from their elements and use it:
Node[] arr = ...; Action def = (arr.length == 1) ? arr[0].getPreferredAction() : null; createPopup(xpos, ypos, Utilities.actionsToPopup(actions, this, def));
The default action is then going to be communicated via a property defaultAction - that is going to be associated on the JMenuItem created as a presenter for the action. ~
Multiselection support is important for context actions. The other types do not need it. Always enabled are indeed always enabled and the callback actions just should not delegate to more than one delegate, that would unnecessary complicate things.
The easy and hopefully useful default behaviour for context actions
shall be some of them
mode: E.g. regarding of how many elements
is selected, if at least one of them provides the desired context data,
then the action is enabled, and if invoked, it will get access to
all data that provided the context. To implement this one needs
just the notion of DataType
, one does not need to care
about the properties of the selection provider at all.
Anything else however gets more complicated. For example to support
just one
mode - where the action is enabled if one element
is selected and provides the data - checking for present of one
data interface is not enough. Doing that could lead to selection
of multiple elements where just one of them would provide the correct
data type in its context. So it is clear that the action framework
needs to be somehow aware of the concept of selected element
.
Shall the selected element be
Node?
Well that is not good idea, as until now we managed to keep the
actions API without a reference to Node and including such rich
API like nodes
would needlessly complicate the learning curve. Moreover not
all views are Node based - the CVS before commit
table is
likely implemented without node backend.
This seems to lead to a need for a new API between the actions and the views using the actions. Somehow they need to inform each other about the multiselection situation. The possible ways to do it include:
elementinterface - would identify one selected element, Node node would be retrofitted to implement it.
Regardless of what solution is finally selected, there is a clear need for an API between the actions framework and the views which needs to be addressed.
Sometimes it is reasonable to let the action to work on previous
selection even if current selection does not have such actions. For
example one may want the Compile One File
action to work
on last selected file even if we are for example in output window
evaluating the errors printed by the compiler. And vice versa,
the jump to next error
action provided by output should
work even if one is already in editor.
To create such context and callback actions one needs to specify,
during their creation that they survive focus change
. Then
the action will remain associated with the last context that is
sufficent to trigger it:
One can specify that newly created action shall survive focus change
in layer as well. Just add attribute surviveFocusChange
to specification of callback and context actions:
<file name="your-module-prefix-YourActionId.instance" > <attr name="instanceCreate" methodvalue="org.netbeans.spi.actions.Factory.context" /> <attr name="surviveFocusChange" boolvalue="true" > </file>
There are multiple levels of such support. If the modules that provide the different actions somehow know about each other, they can agree on a key, define a global callback action based on that key and then, in different contexts (e.g. ActionMaps) each module can register different action. ActionMapKeys - certain keys has been published by the platform and modules can use them to register their own implementation in action maps of their components:
"cloneWindow"
- an action to be executed when a top component is to be cloned"closeWindow"
- an action when a view is about to be closedDefaultEditorKit.copyAction
- copy action handlerDefaultEditorKit.cutAction
- cut action handler"delete"
- delete action handlerDefaultEditorKit.pasteAction
- paste action handler"jumpNext"
- when a next element shall be selected"jumpPrev"
- when a previous element shall be selectedsharinghowever requires that the modules know each other and also that the set of actions that shares the same key is defined at compile time.
There is another level of configurability - completely different actions, not knowing about each other could share the same key if they are enabled in different contexts. For example, one module defines component which has action copy and registers for it keystroke Ctrl-C. Another module defines different component which has action commit and assigns it a key Alt-C. Later however, the end user or someone assembling and branding own app over these modules, decide that the defaults are not ok and that there is a need to that in both components both the actions can be invoked with Ctrl-C. The current system with context and callback actions does not allow the end user or the assembler of the application to do such kind of mappings. To implement this it would be needed to allow one keystroke to map to more actions. The system could know that in the first module component, Ctrl-C maps to copy and in the second module component it maps to commit. There are two solutions how to achive this:
Shortcuts
and Keymaps
folders, but
generally it could work as outlined in
issue 15926.
The system would always picked up the one action that is currently
enabled.
Anyway it is not really clear that this feature is that desirable. Maybe we can live without it and that is why it is not going to be solved for 5.1.
Question (arch-time): What are the time estimates of the work? Answer:The main goal - e.g. no need to rely on SystemAction - has to be achived in one release. Best in promo-h. The API is designed in extensible way and covered by tests, so incremental evolution will be possible in every future release.
Question (arch-quality): How will the quality of your code be tested and how are future regressions going to be prevented? Answer:Obviously there is a lot of unit tests for the behaviour of actions created using API of this module. There are few indicators that are determining if the quality is good:
The sources for the module are in NetBeans CVS in openide/actions directory.
Default answer to this question is:
These modules are required in project.xml file:
XXX no answer for deploy-dependencies
org.openide.util.actions.SystemAction
, this
was recently changed that Actions supports javax.swing.Action
which
is java API standard, and Actions will be improving towards this more and more.
Question (compat-version):
Can your module coexist with earlier and future
versions of itself? Can you correctly read all old settings? Will future
versions be able to read your current settings? Can you read
or politely ignore settings stored by a future version?
Answer:
Yes up to now it is supposed to be compatible with older versions.
java.io.File
directly?
Answer:
No.
Question (resources-layer):
Does your module provide own layer? Does it create any files or
folders in it? What it is trying to communicate by that and with which
components?
Answer:
No in fact. Just concrete action implementation provided by this module
are typically used in xml layers.
Question (resources-read):
Does your module read any resources from layers? For what purpose?
Answer:
There are special folders containing actions in xml layers.
org.openide.util.Lookup
or any similar technology to find any components to communicate with? Which ones?
Answer:
XXX no answer for lookup-lookup
Question (lookup-register): Do you register anything into lookup for other code to find? Answer:XXX no answer for lookup-register
Question (lookup-remove): Do you remove entries of other modules from lookup? Answer:XXX no answer for lookup-remove
System.getProperty
) property?
Answer:
Question (exec-component):
Is execution of your code influenced by any (string) property
of any of your components?
Answer:
A very important aspect for behaviour of runtime behaviour of actions is their
implementation of ActionsEquality
-
It depends implementation of Action.equals(Object)
and
Action.hashCode()
methods. The implementation influences
the behaviour
of actions on multi selections - e.g. when a popup menu for more
than one node is invoked, only actions available on all of them
(tested by equals
method) are shown. Generally
actions are equal if they are created with the same factory method
providing the same arguments and they are assigned to the
same context
.
.
Yet another think that needs to be properly implement is the
sharing of the value for AcceleratorKey
-
the value of Action.ACCELERATOR_KEY
needs to be shared
between all instances that are created by the same factory, using
the same arguments.
No ant tasks.
Question (exec-classloader): Does your code create its own class loader(s)? Answer: No. Question (exec-reflection): Does your code use Java Reflection to execute other code? Answer: Question (exec-privateaccess): Are you aware of any other parts of the system calling some of your methods by reflection? Answer: No. Question (exec-process): Do you execute an external process from your module? How do you ensure that the result is the same on different platforms? Do you parse output? Do you depend on result code? Answer:Not at all.
Question (exec-introspection): Does your module use any kind of runtime type information (instanceof
,
work with java.lang.Class
, etc.)?
Answer:
XXX no answer for exec-introspection
Question (exec-threading): What threading models, if any, does your module adhere to? Answer:Most of the action manipulation shall happen only on AWT event thread. This is going to be enforced by asserts. The constructor can however be called outside of the AWT event thread to lower the load during the action creation and also certain attributes like icon and context less name can also be got from other threads.
Question (security-policy): Does your functionality require modifications to the standard policy file? Answer:XXX no answer for security-policy
Question (security-grant): Does your code grant additional rights to some other code? Answer:XXX no answer for security-grant
XXX no answer for format-types
Question (format-dnd): Which protocols (if any) does your code understand during Drag & Drop? Answer:XXX no answer for format-dnd
Question (format-clipboard): Which data flavors (if any) does your code read from or insert to the clipboard (by access to clipboard on means calling methods onjava.awt.datatransfer.Transferable
?
Answer:
XXX no answer for format-clipboard
XXX no answer for perf-exit
Question (perf-scale): Which external criteria influence the performance of your program (size of file in editor, number of files in menu, in source directory, etc.) and how well your code scales? Answer: This should be irrelevant, as far as I know, Actions shouldn't use any collections of data or something like that. One exception I know is global keymap implementation which scales linear. Question (perf-limit): Are there any hard-coded or practical limits in the number or size of elements your code can handle? Answer:XXX no answer for perf-limit
Question (perf-mem): How much memory does your component consume? Estimate with a relation to the number of windows, etc. Answer:XXX no answer for perf-mem
Question (perf-wakeup): Does any piece of your code wake up periodically and do something even when the system is otherwise idle (no user interaction)? Answer: No. At least I do not know about that. Question (perf-progress): Does your module execute any long-running tasks? Answer: No. Question (perf-huge_dialogs): Does your module contain any dialogs or wizards with a large number of GUI controls such as combo boxes, lists, trees, or text areas? Answer:XXX no answer for perf-huge_dialogs
Question (perf-menus): Does your module use dynamically updated context menus, or context-sensitive actions with complicated and slow enablement logic? Answer:XXX no answer for perf-menus
Question (perf-spi): How the performance of the plugged in code will be enforced? Answer:XXX no answer for perf-spi