Permission is required in order to access the system clipboard.
Let's take a simple example, which displays two
JLists
. The JList
supports a copy action (via its LAF TransferHandler
, if you're curious). You can retrieve the action from its ActionMap
. The ActionMap
is supported by all JComponent
s, and is basically a pool of all Action
s that the component supports. How best to make Edit->Copy (or any other menu item, for that matter) invoke the corresponding action from the appropriate JList
? Note that handling a keyboard shortcut is already taken care of by the JList
's InputMap
, which maps keystrokes to actions in the ActionMap
.First, let's define context to be what component has focus. This may not always be true, but given that keyboard input goes where the focus is, it's reasonable to assume that focus indicates the proper current context.
If all we wanted to do was support mapping a keyboard shortcut to an action, we'd have nothing to do, but we'd like to actually make Edit->Copy do the same thing as that keyboard shortcut. A quick and dirty hack might be to have the menu item generate a fake keystroke sent to the currently focused component, but that only works if the target action has a keystroke associated with it. But that's kind of indirect and doesn't smell very good.
What if the Edit->Copy menu item reflected a sort of delegating action, whose
actionPerformed
logic simply called some other action. Whenever the context (i.e. focus) changes, we check the focused component's ActionMap
for a copy action. Whatever we find, we set as the delegate of the delegating action. If there is no copy action, then the delegating action sets itself disabled.
public class DelegatingAction {
public DelegatingAction(String name) { super(name); }
public void setDelegate(Action a) {
delegate = a;
setEnabled(delegate != null);
}
public void actionPerformed(ActionEvent e) {
if (delegate != null) { delegate.actionPerformed(e); }
}
}
To actually look up the delegates, we can store one or more keys which can be used to identify the actions in the target components. For custom-defined actions, you can set your action keys to whatever is appropriate. In order to handle Swing built-in edit actions, we have to account for a few variations.
TransferHandler
uses hard-coded strings "cut", "copy" and "paste", while text components use a few string constants defined in DefaultEditorKit
, which are different from the TransferHandler
strings (e.g. "copy-to-clipboard"). Since these variants exist, we need to check for all of them, since they all correspond to the same Edit menu items.To know when to update the delegate's mapping, we can install a property change listener to the keyboard focus manager (available in 1.4+ JREs). We only want to look at permanent focus changes, so listen to "permanentFocusOwner".
There's an unexpected glitch caused by the fact that the
TransferHandler
edit actions are singletons shared across all transfer handlers. As such, they expect to deduce the target of the edit operatoin from the ActionEvent
source. In order to work around this, the delegating action must also keep track of the current component context so that it can reformat the ActionEvent
to have the proper source.The singleton implementation also means that the actions' enabled state doesn't reflect whether the target component can actually process the action. Copy should be disabled if nothing is selected, and paste disabled if the component is not editable. Leave that to a future article...
In practice I use a system that's a little more general (it can auto-populate dynamic menus and submenus, and listens for changes on a per-menubar basis instead of per-action, which makes GC a little easier).
Source is here.
2 comments:
This seems to be exactly what I am looking for... but the source link gives me a jarfile with .class files and no source. Would you be willing to post the source code of the demo app?
Thank you so much.
Alex
The source is available at the furbelow project on sourceforge, http://furbelow.sf.net.
Post a Comment