Desing document covering architecture of solutions in actions system
$Revision: 1.5 $
Changes: available in
CVS
Status: issue
17597 Dependency
Tree: of
issue
17597
Abstract
This document discusses the solution to above mentioned problems that is
the best according to current knowledge and respects the current state of
the system. It is expected that this solution will be implemented as standalone
module. Other modules will then be offered its services, but their usage
will be voluntary. Thus backward compatibility will be easier to keep because
changes in current
Actions
API will be minimized. The 3.3 style of actions will continue to work,
but module owners should find switching to the new API atractive because
of its declarative nature and all its improvements including performance.
It is expected that about 90% of existing actions can rewritten to the new
architecture, still using the old style continues to work for actions with
enhanced and complicated presenters (component palette, memory view actions,
workspace switchers, etc.).
Note: Consult this document with current implementation
doc.
Declarative actions
In order to lower the requirements on VM and allow declarative definition
of modules' actions it seems reasonable to reuse the XML layer of modules
and allow actions to be defined there.
One action will be represented as one file. From its attributes and/or
content an instance of javax.swing.Action will be constructed
including localized name, action icon and controls checking the state (enabled/disabled)
and performing actual action when action is invoked.
XML Layer files already come with ability to have localized name and
special icon, the same mechanism is used for icon and name of the action.
The appropriate piece of XML layer will look like this:
<file name="InvokeMyWizard.action">
<!-- to give the localized name & icon -->
<attr name="SystemFileSystem.localizingBundle" stringvalue="..." />
<!-- invoke can either point to a static method that will do the action -->
<attr name="action" methodvalue="mymodule.MyWizard.show" />
<!-- or describe how to create an action for that will handle all requests -->
<attr name="action" newvalue="mymodule.MyWizardAction" />
</file>
Comments from jglick: prefer choice between the attribute action
creating an Action that can be delegated to, and creating an
ActionListener that can be invoked as the action body. In other
words you must return an ActionListener which will be run when
the action presenter is selected; and if it is in fact an Action
as well then this will additionally be used to service all further Action
methods (name, icon, selection state, listeners, and so on) using delegation.
This is cleaner than relying on method side effects, permits the ActionEvent
to be passed even in the simple mode, probably easier to explain, and will
still work if some filesystem layer caches file attributes. Avoiding extra
class creation for the ActionListener is not difficult if you
implement it on classes which would exist anyway, use switch statements,
etc.
Comments from dsimonek: I would take Jesse's note further (get inspired
from Petr Nejedly). Let's don't use methodvalue, use stringvalue and let
action system to invoke method itself, so that actions system is under control
where and when is method invoked. This is safer as client code should be
viewed as dangerous from synchronization point of view.
The extension .action is recognized as description of an
action and specified attributes are read to create appropriate instance of
the Swing action.
There will be a declarative way how to specify when an action is enabled or not. The goal is to provide
rich framework that will compute the enabled state without invoking any custom
code and loading unnecessary classes to VM.
References
Today, when action is represented by an instance of a class (and moreover
subclass of SharedClassObject) we do not encounter problems with references:
One action can be present in menu, toolbar, popup menu and still we deal
just with one action. The reason is simple - file Action.instance
is itself a reference to the actual action class - but this is no longer
true for .action files because they share the same class for
all actions.
That is why there has to be mechanism how different presentation of the
same action (menu, toolbar, shortcut) really share the same action reference.
All actions are stored in some action area which provides natural
pool of all actions sorted by cathegories (and thus suitable for presentation
to the user) and all actions in menu, toolbars, etc. refer to this pool
forcing each module to register its action in the pool (which right now is
not necessary - users are not able to find all actions there). The cathegories
actions are stored in also provide natural naming space that UI occurences
of actions can use to refer to them:
<folder name="Menu/File">
<!--
reference included in a name when name describes the
cathegory and name of action
-->
<file name="Wizards-InvokeMyWizard.action" />
<!-- or in an attribute where name can be anything -->
<file name="StartMyWizard.action">
<attr name="ref" stringvalue="Wizards/InvokeMyWizard" />
</file>
</folder>
The action pool is stored on system file system in folder
Templates/Actions
so other modules can add new actions into it using XML layer and its possible
to offer them to user in New popup menu in Options/.../Menu and Options/../Toolbar
configuration nodes.
Callback actions
Several actions in current
Actions
API - Cut, Copy, Paste, Delete, Find - did a lot of work (according to
a profiler) turning these actions on and off
even when they are not added
to any visible component (i.e. turn off all toolbars, so they should be removeNotify()d).
The only reason for this was that a different class computed their enablement:
in the Explorer, ExplorerActions for the first four, and SearchHook for the
last--which did not know whether the actions were visible or not, and could
not find out if this changed.
The solution to this problem is inspired by InputMaps
and ActionMaps
introduced by Swing
in JDK1.3. We do not need to convert a KeyStroke
to action and that is why we can leave InputMap out of our
focus for now, but it seems excelent to reuse ActionMap for
our callback performers.
Every component (TopComponent)
in the system has the possibility to define its own ActionMap
that maps a key to appropriate Action. When a menu item or
a toolbar button of some action registered via .action file
is pressed (thus action is invoked), a key for the action is retrieved (to
be defined later) and current context (TopComponent) is examined
to contain association between the key and some action in ActionMap.
If some Action is found, its actionPeformed method
is invoked and thus the action serves as our old ActionPerformer.
The action's key will be taken from an attribute of .action
file or from the name of the action in the action pool:
<file name="InvokeMyWizard.action">
<!--
attribute specifying special key that can be ommited in such
case the key="Wizards/InvokeMyWizard"
-->
<attr name="key" stringvalue="wizardKey" />
</file>
When any component needs to override the behaviour of InvokeMyWizard action
in its context it can just define
TopComponent tc;
tc.getActionMap ().put ("wizardKey", new MySpecialAction ());
and invoking the action in context of component ends with invoking of MySpecialAction
instead of the global system one.
Note: There is already an implementation
of context aware callback action, even not in declarative way up to now.
Global Shortcuts
Right now the dispatch of keyevents to global shortcuts is handled
in a way that does not seem to be the one suggested by Swing team - we register
our
FocusManager and handle not consumed actions by consulting
our global
Keymap. This was the only way how to make things
work under
JDK1.2, but
JDK1.3 has come with a concept of
InputMaps
and
ActionMaps
that seem to be more suitable for implementation of global shorcuts. Moreover
implementation of
FocusManager has been enhanced in
JDK1.4
and causes us compatibility problems.
That is why we should investigate the possibility to create one global
InputMap holding all our global shortcuts and attach it to
each TopComponent. If successful we will not need to implement
anything more - the dispatch process will be handled by Swing.
Context
One source of problems in current implementation of Actions
API is that the state of actions is global. Actions have listened on
global state of the system, updated itself according to this state and reflected
it. This is necessary for menu and toolbar, but for popup menus or scripting
invocation a better behaviour is needed - state of action should reflect
its context (a window, selection of nodes, etc.).
First thing that has to be done is the definition of context. The callback actions sections uses ActionMap
to locate the current performer action. That is why ActionMap
has to be part of the context.
Our possition is a bit complicated because we are building our framework
on Swing's Actions API and these actions are not ready to update
their context - they receive context at creation time. We have to enhance
the framework to allow results of actionPeformed and isEnabled
to be based on the actual context of the caller.
The context for a selected TopComponent will contain the component
itself, its ActionMap, all its activated nodes and
also all cookies of those nodes. By enhancing API in this way, any
action can obtain its current context and easily query cookie state without
need to work with nodes. Instead of having to deep down to each node, the
context is now flat. This raises a question how to efectively define declarative
queries over cookies and other content of the Lookup (yet to be specified).
A popup menu for given context can be constructed by cloning actions
and we do not need to worry about focus problems anymore.
Note: Have a look for current context
implementation.
Local Action Pools
The most often example of cooperation between different modules is
the ability to add new actions to foreign components. Editor's popup menu
is such
example. It defines its own directory where other modules can put references
to its actions, specify their order and later editor obtain them and construct
its own popup menu
Note: More specifically, there needs to be created an association between
certain TopComponent type and its action pool in layer.
Every module should be able to define such local action pool
and it should be easy and well defined how to construct the menu, toolbar
or popup menu from it. In NetBeans 3.3 there is an easy way how to construct
menubar
and toolbar pool
toolbar
pool, popup menu is yet to be added.
Problems of the solution
- Tools/Keyboard Shortcuts dialog has to be updated to deal with new
action style
- Usage of
SystemAction should be replaced by javax.swing.Action
in the FileSystem API and Nodes API in order to allow nodes to feed
popup menus
- How to replace well known Callback actions (Cut, Copy, Paste, Find)
so they will be able to use advantages of the new architecture?
- If the actions pool is to be stored in Templates/Actions/, we need
to keep UI compatibility with older modules putting (system) actions into
Actions/ This should be solved by Templates/Actions/Old.shadow
should point here so that these will still be available.
ToolsAction will continue to work, but should be mostly
replaced by local action pools - specifying actions in manifest
will no longer be suggested
Send comments and patches to
nbdev@netbeans.org
or attach them to issue
17597. Please
create the patches to current version of the
document.