Skip to main content

Cut, Copy and Paste

Posted by zixle on August 7, 2006 at 9:27 PM PDT

After a long hiatus I'm returning to a series of blogs on architecting
applications. This time around I'm covering a simple way to provide
rich cut, copy and paste behavior in an application.

As usual, for those wishing to cut to the chase, here's the app:


width="88"
height="23""/>

Many Swing components provide cut, copy and paste support. For
example, JTextField supports cut, copy and paste out the box. This is
hardly revolutionary; todays developers expect such behavior out of
the box.

Swing's cut, copy and paste support is provided by
javax.swing.TransferHandler. TransferHandler provides Actions for cut,
copy and paste. Unfortunately the enabled state of the actions is not
updated based on context, and the actions target the source of the
event. In other words, the actions provided by TransferHandler are not
appropriate for use on menus or toolbars.

While the Actions provided by TransferHandler have limited usage, the
remaining portions of TransferHandler are imminently more useful (even
more so in 1.6). Rather than reinvent the wheel, I'll provide new
Actions that target TransferHandler.

Here's the list of requirements for cut, copy and paste actions:

  • The actions should track the permanent focus owner. For example, you
    would not want the copy action to target a menu when it has focus.

  • The paste action should only be enabled if the clipboard has a data
    flavor supported by the component.

  • The cut and copy actions need to update based on the selection of
    the component. For example, if a text field has focus, the cut and
    copy actions should only be enabled if the selection is not empty. *

Ideally it would be easy to enable this support in an existing
application with minimal fuss. To that end a helper class,
CutCopyPasteHelper, with all static methods is used. The static
methods operate on any JComponent and do not look for specific
interface in determining cut, copy and paste support.

Targetting the focused component is easily done with the
KeyboardFocusManager. The KeyboardFocusManager maintains a bound
property for the permanent focus owner. To listen for changes one need
only install a PropertyChangeListener. The following code illustrates
this:

  PropertyChangeListener focusListener = new PropertyChangeListener {
    public void propertyChange(PropertyChangeEvent e) {
      if (e.getPropertyName() == "permanentFocusOwner") {
        // The permanent focus owner has changed.
        newPermanentFocusOwner((Component)e.getNewValue());
      }
    }
  };
  KeyboardFocusManager.getCurrentKeyboardFocusManager().
      addPropertyChangeListener(focusListener);

The paste action is the trickiest. The paste action is enabled if the
paste action is enabled on the focus component, and a data flavor on
the clipboard matches the one supplied for the focused
component. Tracking the set of data flavors on the clipboard is easily
done using a FlavorListener. The following code illustrates this:
  FlavorListener flavorListener = new FlavorListener {
    public void flavorsChanged(FlavorEvent e) {
      flavorsChanged();
    }
  };
  Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
  clipboard.addFlavorListener(flavorListener);

The set of data flavors a component supports for paste must be
provided by the developer. This is done with the following method:

  CutCopyPasteHelper.registerDataFlavors(component,  DataFlavor...dataFlavors);

In addition to setting the set of flavors, you must explicitly enable
the ability for a component to paste. This is done with the
setPasteEnabled method. Seperating the set of flavors
from the enable state makes it easier to operate on each
independently. For example, once you've registered the flavors, if you
subsequently need to change the paste enabled state you need only
invoke setPasteEnabled.

Once you've enabled paste on the component and registered the set of
flavors, CutCopyPasteHelper has all the information needed to update
the paste action appropriately for you.

The cut and copy actions are simpler. These are updated based on the
focused component, and whether or not you've enabled cut and copy. For
example, a text field with an empty selection would disable the cut
and copy actions. On the other hand, a text field with a non-empty
selection would enable the cut and copy actions. CutCopyPasteHelper
provides the setCutEnabled and setCopyEnabled methods for this.

Many Swing components provide keyboard bindings that target the
actions provided by TransferHandler. CutCopyPasteHelper provides
replacements for these actions. For a component to use
CutCopyPasteHelper actions you must change the bindings registered on
each component to target CutCopyPasteHelper. This is done using the
ActionMap and InputMap. CutCopyPasteHelper provides the
registerCutCopyPasteBindings that takes care of this.

Here's a cheat sheet for using these actions with a component:

  1. Provide a TransferHandler for the component.
  2. Replace the bindings provided by Swing using the
    registerCutCopyPasteBindings method.

  3. Register the set of data flavors supported during paste by way of
    the registerDataFlavors method.

  4. Install a listener on the component that updates the cut and copy
    state based on the component's selection. The cut and copy state is
    changed using the setCutEnabled and setCopyEnabled methods.

Here's the relevant portions of the code for the password store app
that enable cut, copy and paste on entryList (a JList):

  // Step 1
  // Install the TransferHandler.
  entryList.setTransferHandler(new ListTransferHandler());

  // Step 2
  // Register key bindings that target the cut, copy, and paste actions
  // provided by CutCopyPasteHelper.
  CutCopyPasteHelper.registerCutCopyPasteBindings(entryList, true);

  // Step 3
  // Register the data flavors on the list:
  CutCopyPasteHelper.registerDataFlavors(entryList,
                PASSWORD_ENTRY_DATA_FLAVOR);
  // Enable paste
  CutCopyPasteHelper.setPasteEnabled(entryList, true);

  // Step 4
  // Install listener to update cut/copy state based on selection
  ListSelectionListener selectionListener = new ListSelectionListener {
    public void valueChanged(ListSelectionEvent e) {
      if (!e.getValueIsAdjusting()) {
        boolean hasSelection = (entryList.getMinSelectionIndex() != -1);
        CutCopyPasteHelper.setCopyEnabled(entryList, hasSelection);
        CutCopyPasteHelper.setCutEnabled(entryList, hasSelection);
      }
    }
  };
  entryList.addListSelectionListener(new ListSelectionHandler());

To use the actions provided by CutCopyPasteHelper in a menu is no
different than any other action, invoke setAction or use a constructor
that takes an Action. Please note that the name, accelerator,
mnemonic, and icon are not provided. It is expected you'll set these
directly on the menu or button using the action.

* Selection

You'll notice these actions target the focused component. Targetting
the focused component is problematic if you need another component to
get focus while firing the action. For example, if a button wired to
the cut action gets focus, the cut action targets the button; this is
not what you want. This is trivially fixed by invoking
setFocusable(false) on the button, but that's a stop-gap. The larger
issue is how to determine the target of these actions. In nearly all
cases targetting the permanent focus owner is what you want, but there
are exceptions. This is a larger issue, to which many folks have
proposed big solutions. It's something that needs more study, and has
been bothering both href="http://weblogs.java.net/blog/hansmuller">Hans and myself...

The Sandbox

For security reasons a sandboxed app can't get at the system
clipboard. This is most definitely a major pain! I've primarily
concerned myself with apps that have full access to the clipboard. I
suspect there are some things that could be done to make support of
these actions better when in the sandbox. That's for another day though.

Early on I mentioned I wanted it to be trivial to wire these actions
up to existing components. I've done the work for the text
components. As this blog is now gargantuan in size, I'll save that for
a later blog. In the mean time, here's the latest href="http://weblogs.java.net/blog/zixle/archive/2006.08.02/passwordstore.zip">source for the password store app, and href="http://weblogs.java.net/blog/zixle/archive/2006.08.02/fabric.zip">source for fabric. For those that look at the source,
you'll see a bunch of stuff I haven't gotten to. That's for a later
day as well... Oh, and yes, CutCopyPasteHelper will eventually makes it's way
into Swing in some form or another.

    -Scott

Related Topics >>