Last updated: February 3, 1997
Note: The Clipboard API will be included in JDK1.1, however the Drag-and-Drop API will be in the following release (due to time constraints on 1.1). We are including the document on Drag-and-Drop here so that the Java Beans document can refer to it and we can get as much early feedback as possible.
Purpose
Typical GUI users today expect to be able to transfer data between applications using operations like cut/copy/paste and drag-and-drop. The only mechanism for this in the current Java environment is through whatever facilities the AWT native widgets (TextField, TextArea) provide by default. There are many cases however, where an application needs to enable these operations outside of these native widgets, and therefore the Java platform needs to provide an API to enable basic data transfer capabilities. This document defines a baseline data transfer capability for Java objects on top of which various transfer-protocols can be built. This document also describes APIs for two of those higher-level transfer-protocols, Clipboard and Drag-and-Drop.
Java Data Transfer
Design Goals for Transfer API
The design goals for the data transfer API are the following:
java.awt.datatransfer.Transferable
A transferable object must be able to provide a list of formats (called " data flavors") for which it can provide the data, ordered from the most-richly descriptive flavor to the least. It must also be able to return the data (in the form of an Object reference) when requested in a particular flavor (or throw an exception if that flavor is not supported or the data is no longer available).
Convenience classes which implement the Transferable interface for common data types will be provided to make transfer of these common types easy for the developer. For example:
java.awt.datatransfer.StringSelection
The purpose of this API is to ensure that once the work goes into making a particular element or type of data transferable, it can be easily be passed around using any of the higher-level transfer protocols (clipboard, drag-and-drop, etc.).
Data Flavors
A typical aspect of common data transfer operations (clipboard, Drag-and-Drop) is a negotiation between the provider and the requestor on which flavor to transfer the data in. For example, when html text is selected in a browser and then copied/pasted into a separate word-processing application, the browser would typically offer the data in multiple flavors (probably html formatted text and plain ASCII) in order to maximize the number of potential target applications for a paste operation.
This negotiation requires the definition of a data typing name-space such that these various flavors and data-types can be uniquely defined and recognized by distinct applications. In order to avoid the confusion of such overloaded terms such as "format", we have chosen the term "flavor" to represent this concept.
A data flavor is represented by an object which encapsulates all necessary information about a particular flavor to enable flavor negotiation and transfer between applications:
java.awt.datatransfer.DataFlavor
This information includes a logical name for the flavor (to enable programmatic identification), a human-presentable name (which be used to present to the user and would be localizable), and the representation class which is used to define the class of object used to actually transfer the data.
MIME type registration is currently handled by a third-party, the Internet Assigned Numbers Authority (IANA), enabling developers to easily locate the standard type/subtype name used for published data formats. Fortunately, formal registration is not required in order to define new MIME type/subtype names for less common formats (we agree that such a formal requirement would not be acceptable for basic Java data transfer). New type names can be created without formal registration by prepending "x-" in front of the name.
Object getTransferData(DataFlavor flavor)
is loosely defined to return an instance of class "Object" (for maximum flexibility), the DataFlavor's defined representation class becomes important to the receiving end of a transfer operation because it allows the returned object to be decoded unambiguously.
The current DataFlavor class defines two general kinds of data flavors:
MIME-type="application/x-java-serialized-object; class=<implemenation class>"
RepresentationClass=<implemenation class>
e.g. a DataFlavor representing an AWT GUI component:
MIME-type="application/x-java-serialized-object; class=java.awt.Component"
RepresentationClass=java.awt.Component
If the requesting side of a transfer operation asks for the data in this flavor,
it will be handed back an instance of the Component class.
MIME-type="application/<mime-subtype>"
RepresentationClass=java.io.InputStream
e.g. a DataFlavor representing RTF text:
MIME-type="application/rtf"
RepresentationClass=java.io.InputStream
If the requesting side of a transfer operation asks for the data in this fla-vor,
it will be handed back an InputStream instance from which it can read/
parse the RTF formatted text.
For a given MIME-type (type #2 above), a Java program is free to create multiple flavors with different representation classes. For example, in addition to providing the flavor for the MIME-type "application/rtf" above, a program could specify another flavor:
e.g. a DataFlavor representing RTF text:
MIME-type="application/rtf"
RepresentationClass=foobar.fooRTF
The DataFlavor concept may seem complex and confusing, however our intent is to make this as convenient as possible for developers by defining a set of commonly used data flavors.
Multiple Item Transfer
It is not uncommon for a transfer protocol to support the transfer of multiple distinct pieces of data in a single transfer operation (i.e. like a drag-and-drop of multiple file icons from a file manager application). The transfer API needs to support some form of simultaneous transfer of multiple data items and the current proposal is to encapsulate this capability using an implementation of Transferable which can deal with a collection of distinct data objects. This plan is currently under more rigorous investigation and will be discussed in more detail in a future revision of this proposal.
Code Example for Creating a Transferable Object
The following code shows the StringSelection class source, which is an example
of how the API in this proposal would be used to create a class which is capable of transferring plain unicode text.
package java.awt.datatransfer;
import java.io.*;
/**
* A class which implements the capability required to transfer a
* simple java String in plain text format.
*/
public class StringSelection implements Transferable, ClipboardOwner {
final static int STRING = 0;
final static int PLAIN_TEXT = 1;
DataFlavor flavors[] = {DataFlavor.stringFlavor, DataFlavor.plainTextFlavor};
private String data;
/**
* Creates a transferable object capable of transferring the
* specified string in plain text format.
*/
public StringSelection(String data) {
this.data = data;
}
/**
* Returns the array of flavors in which it can provide the data.
*/
public synchronized DataFlavor[] getTransferDataFlavors() {
return flavors;
}
/**
* Returns whether the requested flavor is supported by this object.
* @param flavor the requested flavor for the data
*/
public boolean isDataFlavorSupported(DataFlavor flavor) {
return (flavor.equals(flavors[STRING]) || flavor.equals(flavors[PLAIN_TEXT]));
}
/**
* If the data was requested in the "java.lang.String" flavor, return the
* String representing the selection, else throw an UnsupportedFlavorException.
* @param flavor the requested flavor for the data
*/
public synchronized Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (flavor.equals(flavors[STRING])) {
return (Object)data;
} else if (flavor.equals(flavors[PLAIN_TEXT])) {
return new StringReader(data);
} else {
throw new UnsupportedFlavorException(flavor);
}
}
public void lostOwnership(Clipboard clipboard, Transferable contents) {
}
}
The clipboard architecture relies on the data transfer mechanism defined by the Java Data Transfer API. The Clipboard API includes a single class which implements the data transfer model for a standard clipboard:
java.awt.datatransfer.Clipboard
and an interface which is implemented by any classes which will be writing data to the clipboard:
java.awt.datatransfer.ClipboardOwner
The Clipboard class provides two basic methods for reading-from/writing-to the clipboard:
void setContents(Transferable content, ClipboardOwner owner)
Transferable getContents(Object requestor)
The ClipboardOwner interface consists of a single method which is called if another object asserts ownership of the clipboard:
void lostOwnership(Clipboard clipboard)
To make the job of implementing clipboard operations on common data types easy for the developer, there will be convenience classes provided which implement the ClipboardOwner interface in a standard way:
java.awt.datatransfer.StringSelection
The following method in java.awt.Toolkit will provide access to the clipboard instance which interacts with the native platform facilities:
Clipboard getSystemClipboard();
The general sequence for implementing "paste":
(note: for simplicity this example uses a TextArea as the source/dest for the copy/paste operation; on most platforms, cut/copy/paste is already implemented for the TextArea and TextField classes within the native peers)
import java.awt.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
public class ClipboardTest extends Frame
implements ClipboardOwner, ActionListener {
TextArea srcText, dstText;
Button copyButton, pasteButton;
Clipboard clipboard = getToolkit().getSystemClipboard();
public ClipboardTest() {
super("Clipboard Test");
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setLayout(gridbag);
srcText = new TextArea(8, 32);
c.gridwidth = 2;
c.anchor = GridBagConstraints.CENTER;
gridbag.setConstraints(srcText, c);
add(srcText);
copyButton = new Button("Copy Above");
copyButton.setActionCommand("copy");
copyButton.addActionListener(this);
c.gridy = 1;
c.gridwidth = 1;
gridbag.setConstraints(copyButton, c);
add(copyButton);
pasteButton = new Button("Paste Below");
pasteButton.setActionCommand("paste");
pasteButton.addActionListener(this);
pasteButton.setEnabled(false);
c.gridx = 1;
gridbag.setConstraints(pasteButton, c);
add(pasteButton);
dstText = new TextArea(8, 32);
c.gridx = 0;
c.gridy = 2;
c.gridwidth = 2;
gridbag.setConstraints(dstText, c);
add(dstText);
pack();
}
public void actionPerformed(ActionEvent evt) {
String cmd = evt.getActionCommand();
if (cmd.equals("copy")) {
// Implement Copy operation
String srcData = srcText.getText();
if (srcData != null) {
StringSelection contents = new StringSelection(srcData);
clipboard.setContents(contents, this);
pasteButton.setEnabled(true);
}
} else if (cmd.equals("paste")) {
// Implement Paste operation
Transferable content = clipboard.getContents(this);
if (content != null) {
try {
String dstData = (String)content.getTransferData(
DataFlavor.stringFlavor);
dstText.append(dstData);
} catch (Exception e) {
System.out.println("Couldn't get contents in format: "+
DataFlavor.stringFlavor.getHumanPresentableName());
}
}
}
}
public void lostOwnership(Clipboard clipboard, Transferable contents) {
System.out.println("Clipboard contents replaced");
}
public static void main(String[] args) {
ClipboardTest test = new ClipboardTest();
test.show();
}
}
Note: The Drag-and-Drop API will NOT be included in JDK1.1 due to time constraints for this release.
Design Goals for Drag and Drop API
Following are the design goals of this API:
Each native platform provides a slightly different model and feature set for drag-and-drop. This API is closest in combination of features and behavior to OLE drag-and-drop. This architectural decision was not driven by the large number of windows users, but rather from the understanding that a design constrained by Motif's limitations would be unacceptable, while one encompassing all of Macintosh drag-and-drop's functionality would be unimplementable.
Drag and Drop API Overview
Components declare themselves as capable of being dragged from by implementing the interface:
java.awt.dnd.DragSource
DragSources are responsible for the following:
Components declare themselves as capable of receiving a drop by implementing the interface:
java.awt.dnd.DropTarget
DropTargets are responsible for the following:
dropEnter() exists to allow the target to cache state about the compatibility of the DragSource's DragFlavors and data in order to increase the performance of dropOver(). This is desirable due to the number of times that the DropTarget's dropOver() method will be called in comparison to dropEnter().
If the DropTarget provides drag under feedback to the user, it usually does so in dropOver(). The goal of this feedback is to give the user a clearer idea of what the outcome of a drop at the current drag location would be. An example of this would be a text editor DropTarget which followed the location of text dragged over it with an insertion caret, showing the user the exact location at which the text would be inserted if dropped.
The AWT implementation calls the DropTarget's dropEnter() method whenever a drag enters the DropTarget. Thereafter, dropOver() is called whenever the drag moves over the DropTarget, or the user indicates that he wishes to change the action to be performed in response to a drop. This continues until the drag exits the DropTarget.
[We may wish to guarantee that dropOver() is always called at least once if dropEnter() is called. This would often generate an extra dropOver(), reducing performance, but would make the programming model a little easier]
On Motif, there are some problems with generating the "pulse" for dropScroll() that still need to be worked out.
Note: ACTION_LINK has been excluded in order to reduce the complexity of the API.
Of course, simply copying the objects that the target wants from the source won't work when dragging and dropping between different applications or Java Virtual Machines. What actually happens in each case is much more complex and is outlined below using OLE drag-and-drop as an example of the native drag-and-drop mechanism.
In order to be transferred by drag-and-drop, a Java object must support the Transferable interface. This interface is responsible for enumerating the different DataFlavors supported by an object (getTransferDataFlavors() method of the Transferable interface) and returning a new object that represents a given DataFlavor of the original object (getTransferData() method of the Transferable interface.)
When a DragSource adds a Java object to the collection of objects to be dragged, the AWT implementation wraps an IDataObject interface around the object. IDataObject->EnumFormatEtc() is wired up to the object's getTransferFormats() method and IDataObjectGetData() is wired up to getTransferData().
The values returned by EnumFormatEtc() are determined by the MIME types of the DataFlavors supplied by the DragSource. Each MIME type string is looked up in a registry mapping MIME types to Windows clipboard formats. If a mapping is found (for example, text/plain = CF_TEXT), the mapping is used for the value of FormatEtc.cfFormat. Otherwise, the value returned from calling RegisterClipboardFormat() on the MIME type is used.
When the target requests a specific DataFlavor of one of the dragged Java objects from the source, the getTransferData() method of the dragged object is called with the specified DataFlavor. It should do one of two things:
This is the behavior for implementing drag-and-drop under Windows, but it is essentially similar for Macintosh and Motif. The key is that the behavior of the DragSource and DropTargets sides are always independent of one another--they don't care if the other side is another Java application or not. The DragSource side always translates the Java objects and DataFlavors into native drag-and-drop concepts, while the DropTarget always translates native drag-and-drop into Java. This separation puts drag-and-drop between Java Applications on par with drag-and-drop between Java and native applications. At a later point, we can always increase performance when the DragSource and DropTarget are within the same Java Virtual Machine by short-circuiting the serialization and simply cloning the objects where possible.
For example, if the user drags a rectangle and a text field from a graphical layout editor over emacs, emacs should return feedback indicating that it would not accept a drop because it could accept the text field's contents, but not the rectangle's.
[It is still unclear whether the individual objects should be exposed as they are now, or whether an approach closer to OLE's, where an enumeration is passed should be used]
java.awt.dnd.DragContext
java.awt.dnd.DropContect
The DragContext is created and returned by the DragSource's dragBegin() when a drag begins. It contains the list of objects and allowable actions for the drop target for this drag. In addition, it also supports the following functions to be called inside of the DragSource's dragOver() method:
The default AWT implementation used when dragOver() returns FALSE, calls getSuggestedAction() and passes the result to setCursor(). (described below)
The DropContext is created by the drag-and-drop implementation and passed to all of the DropTarget methods. It supplies the following functions for determining information about the drag:
The action returned by getSuggestedAction() is guaranteed to be compatible with the actions allowed by the drag source. If the action suggested by the user isn't one of the actions allowed by the drag source, it is transformed into the action returned by getSuggestedAction() as follows:
- ACTION_COPY -> ACTION_NONE
-ACTION_MOVE -> ACTION_COPY
The DropTarget may wish to return an action different than the suggested action if it doesn't support the complete suite of actions. For example, a file manager may only allow files to be copied between volumes, never moved.
The source has final say in what action is actually performed when a drop occurs because the source is responsible for performing the additional processing necessary to carry out the MOVE action.
If the object represents a non-Java object, like a GIF file, the DropTarget will receive an InputStream. The DropTarget can then use the InputStream to retrieve all of the object's data.
It is possible that the AWT implementation could translate non-Java formats into Java constructs on request to make the Java programmers life easier. For example, a GIF file dragged from Adobe Photoshop ontoa Java application could possibly be converted into a Java ImageProducer by the underlying AWT implementation, while a street address dragged out of a Windows mail merge application may be represented by a simple InputStream containing the serialized version of the address.
DragSources and DropTargets can limit their promiscuity by restricting themselves to private DataFlavors, but a more straightforward design might add a method to the DragContext to get the current DropTarget and a method to the DropContext to retrieve the current DragSource. These routines would return NULL if the drag wasn't within the same Java VM. This would make it possible to implement user interfaces such as a pair of text lists where each itme is either in one text list or the other.