|
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 seem 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 managment, 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. 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:
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.api.actions.Factory.delegate" /> <attr name="delegate" newvalue="your.module.Listener" > </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 branding mechanism for
layers - e.g. SystemFileSystem.localizingBundle
attribute, etc.
One can define a placeholder action - e.g. an action that has the name, icon, shortcut, but does not have the actual implementation. Such implementation 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.api.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.
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:
one is called on AWT thread and returns a ProgressHandle
,
seconds is called outside of the thread and gets handle as an argument.
Third method 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.api.actions.Factory.context" /> <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 and 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 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.
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
.
It seems reasonable to keep the actions framework as far from
nodes as
possible. That is why there is a query
interface with one
method Action findDefaultAction(Lookup context)
define in the new action API and implemented in Node API and also
anywhere else where such implementation is desirable. This interface
is then use by default presenters of the actions to draw the one
selected in bold.
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.
TopComponent.getActivatedNodes()
.
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.api.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. To implement this it would be needed to have more keystroke to action mappings. For example for each component. This is possible, but a bit complicated especially with respect to multi keystroke shortcuts - as found out during the multikeys support review there is a need to for all the keystroke mappings to always share the common prefix. As this seems to be complicated, this is not going to be done 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-g. 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.
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:
Question (exec-ant-tasks):
Do you define or register any ant tasks that other can use?
Answer:
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
Built on December 13 2005. | Portions Copyright 1997-2005 Sun Microsystems, Inc. All rights reserved.