|
[Table of Contents]
[Previous: Windows and
Dialogs] [Next: The
Clipboard]
Feature Owner: Mike
Pinkerton
Overview
As far as Mozilla is concerned, transfering data between
applications via the clipboard and drag and drop are
virtually identical, even though the OS-specific mechanisms
under the hood are very different. The goal is to provide a
uniform API for inter- and intra-application communication
regardless of the transport mechanism used. This document
covers that API, and subsequent documents describe the
various services in detail.
The meat of this API is nsITransferable,
an interface to an object that contains various
representations, or "flavors," of a piece of data. Each
transferable holds only one item and its various
flavors.
- Data Flavors
- Data Objects
- Converters
- Using The Transferable
Data Flavors
Each piece of data, such as a hunk of text or a single
bookmark item in a tree, can (and should) have multiple
representations of the data of varying (usually decreasing)
fidelity. This allows other applications to still be able to
process the data even if they don't understand the richest
representation used by mozilla. For example, the hunk of
text might have html styled text, the stripped-down plain
text representation, and possibly a gif illustrating the
text. Mozilla knows what to do with the styled html, but
SimpleText, for example, does not, yet we still want to be
able to communicate with SimpleText.
Each representation of the data is referred to as a
"flavor." When referring to
data in the transferable, you need to know the flavor. When
you can accept a variety of flavors, there are mechanisms
for asking for the best one.
Important:
Even though there is a flavor "text/plain" to
represent plain ASCII text, clients of the Clipboard and
the Drag and Drop Services should not use it -- use
"text/unicode" instead. Conversion to and from
"text/plain" is performed automatically within these
services. Assume all your data is double-byte and life
will be pure and good. Please fix any Clipboard/D&D
code that uses "text/plain" directly ASAP, as it is not
guaranteed to work in the future.
Data Objects
In order to work with XPConnect and allow writing
clipboard/drag&drop code in JavaScript, the actual must
be wrapped in typed objects. The two most common of these
objects are nsISupportsString
and nsISupportsWString
for one byte and two byte strings, respectively.
These "wrapper" objects must be created using the
component manager and the actual data to transfer must be
placed within them before passing the data to the
Transferable object. Here's a code snipped in JavaScript to
do this.
// create the wrapper and QI it to the right interface
var wrapper =
Components.classes["component://netscape/supports-string"].createInstance(Components.interfaces.nsISupportsString);
if ( wrapper ) {
// ... get the data, place it in |id|
wrapper.data = id;
}
|
Converters
<<talk about data converters>>
Using The Transferable
Placing Data Into The Transferable
The basic usage pattern for adding data to the
transferable is as follows:
- Create the Transferable object
- Register the approprate data flavors
- Create the data objects for each flavor (as
above)
- Add the data objects to the transferable
- Pass the transferable to a service that will use the
data, such as the clipboard or drag&drop
services
Note that the flavor must be registered with the
transferable before data of that type can be added.
[Why? That seems quite stupid.
Shouldn't it just add it if it can't find it?]
Also note that the length parameter of setTransferData() is
in bytes, not characters, so for double-byte strings, you
need to make sure you do the math correctly.
// 1. create the transferable
var trans =
Components.classes["component://netscape/widget/transferable"].createInstance(Components.interfaces.nsITransferable);
if ( trans ) {
// 2. register the data flavors
trans.addDataFlavor("text/html");
trans.addDataFlavor("text/unicode");
// 3. create the data objects
var textWrapper =
Components.classes["component://netscape/supports-wstring"].createInstance(Components.interfaces.nsISupportsWString);
var htmlWrapper =
Components.classes["component://netscape/supports-wstring"].createInstance(Components.interfaces.nsISupportsWString);
if ( textWrapper && htmlWrapper ) {
// get the data
textWrapper.data = plainTextRepresentation;
htmlWrapper.data = htmlRepresentation;
// 4. add data objects to transferable
trans.setTransferData ( "text/html", htmlWrapper, id.length*2 ); // double byte data (len*2)
trans.setTransferData ( "text/unicode", textWrapper, id.length ); // double byte data (len*2)
}
}
|
Retrieving Data From The Transferable
There are two basic cases from retrieving data from the
transferable: when you know exactly what you want and when
you have a list of several flavors you support and you want
the best one available (the more common case, probably). In
either case, the basic steps are similiar:
- Create the Transferable object
- Register the approprate data flavors (see below)
- Pass the transferable a service that will fill in the
data, such as the clipboard or drag and drop
service.
- Ask the transferable for the data (see below)
The only tricky part (which only applies to JavaScript)
is that since the objects returned by
get[Any]TransferData()
are out parameters, you must create new JS objects to hold
the result and the length. After these objects are filled
in, you can get at the actual out parameter values by
accessing the value member of
these objects.
When you know exactly what you're looking for
This is the simple case or the case where you only care
about one flavor. For this, use
getTransferData().
// 1. create the transferable
var trans =
Components.classes["component://netscape/widget/transferable"].createInstance(Components.interfaces.nsITransferable);
if ( trans ) {
// 2. register the data flavor you want
trans.addDataFlavor("text/unicode");
// 3. ...pass transferable to clipboard, etc...
// 4. ask transferable for the data. Need to create new JS
// objects for the out params.
var dataObj = new Object();
var len = new Object();
trans.getTransferData ( "text/unicode", dataObj, len );
if ( dataObj )
dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsWString);
if ( dataObj ) {
// do something with the data
var id = dataObj.data;
}
}
|
When you want the best available
The most common case is where a client supports a variety
of flavors (mozilla flavors plus some from other
applications), but certainly has a preference about which
flavors it wants have if they are present. For this, use
getAnyTransferData().
In order for
getAnyTransferData() to work
correctly, the order in which you register them is very
important. You must register the flavors you are
interested in from most interested to least interested
(usually highest fidelity to lowest, but not necessarily).
Haphazardly registering flavors in random order will cause
you to not get the flavor you are expecting.
// 1. create the transferable
var trans =
Components.classes["component://netscape/widget/transferable"].createInstance(Components.interfaces.nsITransferable);
if ( trans ) {
// 2. register the data flavors you want, highest fidelity first!
trans.addDataFlavor("text/html");
trans.addDataFlavor("text/unicode");
// 3. ...pass transferable to clipboard, etc...
// 4. ask transferable for the best flavor. Need to create new JS
// objects for the out params.
var dataObj = new Object();
var bestFlavor = new Object();
var len = new Object();
trans.getAnyTransferData ( bestFlavor, dataObj, len );
if ( bestFlavor.value == "text/html" ||
bestFlavor.value == "text/unicode" ) {
if ( dataObj )
dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsWString);
if ( dataObj ) {
// ...do something with the data. remember len is in bytes, not chars
var id = dataObj.data.substring(0, len.value / 2);
}
}
}
|
[Table of Contents]
[Previous: Windows and
Dialogs] [Next: The
Clipboard]
Contact us at xptoolkitstaff@netscape.com.
Want to complain about the new documentation? Email
Dave Hyatt.
|