|
Welcome to the Core Java Technologies Tech Tips for October 14,
2003. Here you'll get tips on using core Java technologies and
APIs, such as those in Java 2 Platform, Standard Edition (J2SE).
This issue covers:
Introduction to the Java3D API
Using the TransferHandler
These tips were developed using Java 2 SDK, Standard Edition, v 1.4.
This issue of the Core Java Technologies Tech Tips is written by
Daniel H. Steinberg, Director of Java Offerings for Dim Sum
Thinking, Inc, and editor-in-chief for java.net.
See the Subscribe/Unsubscribe note at the end of this newsletter
to subscribe to Tech Tips that focus on technologies and products
in other Java platforms.
INTRODUCTION TO THE JAVA3D API
The September 9, 2003 Tech Tip
on AffineTransform
introduced some of the geometry behind Java 2D API
transformations. This tip introduces the Java3D API through three
coding examples. In one example, you'll see how to use the
Java 3D API to put an object on the screen and position it.
A second example shows how to use the Java 3D API to set an
object in motion. And a third example shows how to use the API to
apply lighting to a scene.
The Java 3D API is a J2SE optional package that is currently
available in Solaris and Windows as well as other platforms. You
can download the implementation for your computer from the
Java 3D API homepage.
That page also provides links to tutorials, demonstrations, and
sample code -- it also includes a link to the other platforms on
which the Java 3D API is available.
Using Java 3D API constructs, you create a 3D virtual world in
which you can build and manipulating 3D structures. A familiar
analogy might be to an XML document. When you see an XML document
rendered in a browser you focus on the content and may not be
aware of the underlying tree structure. Similarly the 3D scene
information that you view in a Java 3D application as objects in
space are also stored in a hierarchy of nodes known as a scene
graph. The nodes represent objects, information about position or
movement, and information about appearance and lighting.
At the root of this treelike structure is a BranchGroup object.
You will need to associate this object with the Canvas3D object
that is used to render the scene. For consistency with the
examples distributed with Java 3D, this root of the scene graph
is named objRoot in each of the examples in this tip.
There are three fundamental steps in creating a 3D world:
- Create a
Canvas3D object.
- Create a scene graph.
- Connect the
Canvas3D object to a BranchGroup object that points
to the root of the scene graph.
These three steps make up the body of the constructor in each of
the examples in this tip.
In the first example program below, Static3DWorld, the
createCanvas3D() method adjusts the size of a JFrame. The method
then creates a canvas3D object and adds it to the center of the
JFrame. The Java 3D API specific task is to call the static
method getPreferredConfiguration(), and pass the result into the
Canvas3D constructor.
The major action in the example is contained in the
createSceneGraph() method. The BranchGroup object, objRoot,
points to the root of the scene graph. The rotator object is a
TransformGroup that rotates an object Pi/4 over the x axis and
Pi/4 over the y axis. This TransformGroup is added as a child of
the objRoot.
Next a unit cube with each of its six sides emitting a different
color is created. This ColorCube is one of the Java 3D API
primitives. The ColorCube is added as a child of the rotator
object. The createSceneGraph() returns a handle to its root.
Here's the code for Static3DWorld:
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.geometry.ColorCube;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Canvas3D;
import javax.swing.JFrame;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
public class Static3DWorld extends JFrame {
private Transform3D rotate1 = new Transform3D();
private Transform3D rotate2 = new Transform3D();
public Static3DWorld() {
super("Static3DWorld");
Canvas3D canvas3D = createCanvas3D();
BranchGroup scene = createSceneGraph();
connect(canvas3D, scene);
}
private Canvas3D createCanvas3D() {
setSize(300, 300);
getContentPane().setLayout(new BorderLayout());
GraphicsConfiguration config =
SimpleUniverse.getPreferredConfiguration();
Canvas3D canvas3D = new Canvas3D(config);
setSize(300, 300);
getContentPane().add(canvas3D);
return canvas3D;
}
private BranchGroup createSceneGraph() {
BranchGroup objRoot = new BranchGroup();
TransformGroup rotator = new TransformGroup(
rotateCube());
objRoot.addChild(rotator);
rotator.addChild(new ColorCube(0.3));
objRoot.compile();
return objRoot;
}
private Transform3D rotateCube() {
rotate1.rotX(Math.PI / 4.0d);
rotate2.rotY(Math.PI / 4.0d);
rotate1.mul(rotate2);
return rotate1;
}
private void connect(Canvas3D canvas3D,
BranchGroup scene) {
SimpleUniverse simpleU =
new SimpleUniverse(canvas3D);
simpleU.getViewingPlatform().
setNominalViewingTransform();
simpleU.addBranchGraph(scene);
}
public static void main(String[] args) {
new Static3DWorld().setVisible(true);
}
}
After you compile and run Static3DWorld, you should see the
following displayed:
Here's a second example that uses the Java 3D API. The example
program that follows, Spinning3DWorld, set the cube in motion.
Again, the place to begin your examination of the program is the
createSceneGraph() method.
private BranchGroup createSceneGraph() {
BranchGroup objRoot = new BranchGroup();
TransformGroup spinner = new TransformGroup();
spinner.setCapability(
TransformGroup.ALLOW_TRANSFORM_WRITE);
objRoot.addChild(spinner);
spinner.addChild(new ColorCube(0.3));
spinner.addChild(makeSpin(spinner));
return objRoot;
}
Notice that the scene graph is a bit more complex than in the
first example. The spinner is a TransformGroup that is allowed to
write back its transform information. Without this line the
rotation would be calculated, but would not be represented on the
screen. As before, the spinner is added as a child of objRoot,
and a ColorCube is created and added as a child of spinner.
However, this time the spinner has a second child that takes care
of the rotation. The RotationInterpolator object is returned from
the makeSpin() method.
private RotationInterpolator makeSpin(TransformGroup
spinner) {
RotationInterpolator rotator =
new RotationInterpolator(new Alpha(-1, 3000),
spinner);
rotator.setAxisOfRotation(rotateCube());
BoundingSphere bounds = new BoundingSphere();
rotator.setSchedulingBounds(bounds);
return rotator;
}
A new RotationInterpolator is constructed for the spinner that is
passed in. The -1 indicates that it will loop until the program
is terminated. Decreasing the second number from 3000 speeds up
the rotation, increasing it slows down the rotation. The
rotateCube() method from the previous example is used here to set
the axis of rotation.
Here's the code for Spinning3DWorld:
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.geometry.ColorCube;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Alpha;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.BoundingSphere;
import javax.swing.JFrame;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
public class Spinning3DWorld extends JFrame {
private Transform3D rotate1 = new Transform3D();
private Transform3D rotate2 = new Transform3D();
public Spinning3DWorld() {
super("Spinning3DWorld");
Canvas3D canvas3D = createCanvas3D();
BranchGroup scene = createSceneGraph();
connect(canvas3D, scene);
}
private Canvas3D createCanvas3D() {
setSize(300, 300);
getContentPane().setLayout(new BorderLayout());
GraphicsConfiguration config =
SimpleUniverse.getPreferredConfiguration();
Canvas3D canvas3D = new Canvas3D(config);
setSize(300, 300);
getContentPane().add(canvas3D);
return canvas3D;
}
private BranchGroup createSceneGraph() {
BranchGroup objRoot = new BranchGroup();
TransformGroup spinner = new TransformGroup();
spinner.setCapability(
TransformGroup.ALLOW_TRANSFORM_WRITE);
objRoot.addChild(spinner);
spinner.addChild(new ColorCube(0.3));
spinner.addChild(makeSpin(spinner));
return objRoot;
}
private RotationInterpolator makeSpin(
TransformGroup spinner) {
RotationInterpolator rotator =
new RotationInterpolator(new Alpha(-1, 3000),
spinner);
rotator.setTransformAxis(rotateCube());
BoundingSphere bounds = new BoundingSphere();
rotator.setSchedulingBounds(bounds);
return rotator;
}
private Transform3D rotateCube() {
rotate1.rotX(Math.PI / 4.0d);
rotate2.rotY(Math.PI / 3.0d);
rotate1.mul(rotate2);
return rotate1;
}
private void connect(Canvas3D canvas3D,
BranchGroup scene) {
SimpleUniverse simpleU =
new SimpleUniverse(canvas3D);
simpleU.getViewingPlatform().
setNominalViewingTransform();
simpleU.addBranchGraph(scene);
}
public static void main(String[] args) {
new Spinning3DWorld().setVisible(true);
}
}
After you compile and run Spinning3DWorld, you should see the
the same ColorCube that you displayed with the Static3DWorld
program, but this time the cube should rotate. Here's a display
captured at one instant in time:
Now for a final example. In this example, Text3DWorld, let's
replace the the spinning ColorCube with three dimensional text.
This adds a little complexity. The construction of the Text3D
object requires a few more details than what you might be used to
when working with Fonts. You have more options and could customize
the shape in more ways by altering the texture or material of the
Shape3D object. Here is a simple case that mainly uses the
default values.
private Shape3D createTextShape() {
Appearance textAppear = new Appearance();
textAppear.setMaterial(new Material());
Font3D font3D = new Font3D(new Font("Helvetica",
Font.PLAIN, 1),
new FontExtrusion());
Text3D textGeom = new Text3D(font3D,
new String("Text3DWorld"));
textGeom.setAlignment(Text3D.ALIGN_CENTER);
Shape3D textShape = new Shape3D();
textShape.setGeometry(textGeom);
textShape.setAppearance(textAppear);
return textShape;
}
The chief difference in this example as compared to the previous
example is that you need to light the object. If you do not
provide lighting, the spinning text will not be visible. You have
three basic choices for lights: AmbientLight, DirectionalLight,
and PointLight. This example uses DirectionalLight. You specify
the direction of the light as a vector of floats. Similarly, you
must specify the color using a red, green, blue triple of floats
between 0 and 1. If you project more than one light source on an
object the light values are summed but each color is clipped at 1.
private void setLighting(TransformGroup objMove) {
DirectionalLight light =
new DirectionalLight();
light.setInfluencingBounds(
new BoundingSphere());
light.setDirection(
new Vector3f(0.0f,0.0f,-1.0f));
light.setColor(
new Color3f(0.0f, 1.0f, 1.0f));
objMove.addChild(light);
}
Here's the code for Text3DWorld:
import com.sun.j3d.utils.universe.SimpleUniverse;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Alpha;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Appearance;
import javax.media.j3d.Material;
import javax.media.j3d.Font3D;
import javax.media.j3d.FontExtrusion;
import javax.media.j3d.Text3D;
import javax.media.j3d.Shape3D;
import javax.media.j3d.DirectionalLight;
import javax.swing.JFrame;
import javax.vecmath.Vector3f;
import javax.vecmath.Color3f;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
import java.awt.Font;
public class Text3DWorld extends JFrame {
private Transform3D rotate1 = new Transform3D();
private Transform3D rotate2 = new Transform3D();
public Text3DWorld() {
super("Text3DWorld");
Canvas3D canvas3D = createCanvas3D();
BranchGroup scene = createSceneGraph();
connect(canvas3D, scene);
}
private Canvas3D createCanvas3D() {
setSize(300, 300);
getContentPane().setLayout(new BorderLayout());
GraphicsConfiguration config =
SimpleUniverse.getPreferredConfiguration();
Canvas3D canvas3D = new Canvas3D(config);
setSize(300, 300);
getContentPane().add(canvas3D);
return canvas3D;
}
public BranchGroup createSceneGraph() {
BranchGroup objRoot = new BranchGroup();
TransformGroup mover = moveTextBack();
TransformGroup spinner = createSpinner();
objRoot.addChild(mover);
mover.addChild(spinner);
spinner.addChild( createTextShape());
spinner.addChild(makeSpin(spinner));
setLighting(mover);
return objRoot;
}
private TransformGroup createSpinner() {
TransformGroup spinner = new TransformGroup();
spinner.setCapability(TransformGroup.
ALLOW_TRANSFORM_WRITE);
return spinner;
}
private TransformGroup moveTextBack() {
Transform3D transform3D = new Transform3D();
transform3D.setTranslation(
new Vector3f(0.0f, 0.0f, -5.0f));
return new TransformGroup(transform3D);
}
private Shape3D createTextShape() {
Appearance textAppear = new Appearance();
textAppear.setMaterial(new Material());
Font3D font3D = new Font3D(
new Font("Helvetica", Font.PLAIN, 1),
new FontExtrusion());
Text3D textGeom = new Text3D(font3D,
new String("Text3DWorld"));
textGeom.setAlignment(Text3D.ALIGN_CENTER);
Shape3D textShape = new Shape3D();
textShape.setGeometry(textGeom);
textShape.setAppearance(textAppear);
return textShape;
}
private void setLighting(
TransformGroup objMove) {
DirectionalLight light =
new DirectionalLight();
light.setInfluencingBounds(
new BoundingSphere());
light.setDirection(
new Vector3f(0.0f,0.0f,-1.0f));
light.setColor(new Color3f(
0.0f, 1.0f, 1.0f));
objMove.addChild(light);
}
private RotationInterpolator makeSpin(
TransformGroup spinner) {
RotationInterpolator rotator =
new RotationInterpolator(
new Alpha(-1, 3000), spinner);
rotator.setTransformAxis(rotateCube());
BoundingSphere bounds =
new BoundingSphere();
rotator.setSchedulingBounds(bounds);
return rotator;
}
private Transform3D rotateCube() {
rotate1.rotX(Math.PI / 4.0d);
rotate2.rotY(Math.PI / 3.0d);
rotate1.mul(rotate2);
return rotate1;
}
private void connect(Canvas3D canvas3D,
BranchGroup scene) {
SimpleUniverse simpleU =
new SimpleUniverse(canvas3D);
simpleU.getViewingPlatform().
setNominalViewingTransform();
simpleU.addBranchGraph(scene);
}
public static void main(String[] args) {
new Text3DWorld().setVisible(true);
}
}
After you compile and run Text3DWorld, you should see the
the rotating text Text3DWorld. Here's a display captured
at one instant in time:
The objective in this tip was to add complexity, a little at
a time. In the first example, you created and displayed a three
dimensional object. Then things got a little more complex in the
second example, where you rotated the object and set it spinning.
The third example added a little more complexity. In that
example, you rotated an object that required lighting. By using
small modular methods you can easily build much more complicated
examples.
For more information about the Java 3D API, see the Java 3D API
Tutorial.
USING THE TRANSFERHANDLER
The March 18, 2003 Tech Tip
"Dragging Text and Images with Swing"
described how to add drag and drop support to a program so that
text and images could be dragged and dropped between various
Swing components. In that tip, what the receiving components
could accept was already known. Furthermore, how the components
would handle the dropped objects was already known. But what if
that information isn't known beforehand? The following tip covers
that latter situation.
The March Tech Tip introduced the javax.swingTransferHandler
class. You learned that any Swing component associated with
a TransferHandler object can automatically receive a drop target.
That tip relied on a default TransferHandler object. In the
following tip, you customize a TransferHandler object. In doing
that, you indicate what types of objects can be dropped on a
component, and you specify what should be done in the event of an
allowable drop.
First let's start with a program, Drop, that allows you to drag
and drop text. The program creates a JFrame that contains a
JEditorPane. When you run Drop, you can type text into the
JEditor pane. Then you can select the text, drag it, and release
it. You can also select text from another application and drag it
into the text area. As you select and drag text, you should
notice the familiar rectangle indicating that you are dragging an
item. You should also see a target plus sign indicating that you
are in an area where dropping is allowed.
import javax.swing.text.JTextComponent;
import javax.swing.JFrame;
import javax.swing.JEditorPane;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.BorderLayout;
class Drop extends JFrame {
private JTextComponent jText;
public Drop() {
super("Drag n Drop Demo");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300, 400);
createTextArea();
createClearButton();
}
private void createTextArea() {
jText = new JEditorPane();
getContentPane().add(
jText, BorderLayout.CENTER);
}
private void createClearButton() {
JButton clear = new JButton("Clear");
clear.addActionListener(new Clearer());
getContentPane().add(
clear, BorderLayout.SOUTH);
}
private class Clearer implements
ActionListener {
public void actionPerformed(ActionEvent e) {
jText.setText("");
}
}
public static void main(String[] args) {
new Drop().setVisible(true);
}
}
Now try dragging and dropping a file onto the JEditorPane. Notice
that the JEditorPane does not accept the dropped file. To change
that behavior, you need to create a custom TransferHandler. To do
that, you override two methods in the TransferHandler class:
importData() and canImport(). The canImport() method is how a
JComponent knows what it can accept. For example, the following
allows the JComponent to accept anything that is dropped on it:
public boolean canImport(JComponent c,
DataFlavor[] flavors) {
return true;
}
The importData() method is where you implement the actions in
response to a drop. Usually this involves interacting with the
Transferable object dropped on the JComponent.
Here is a customized TransferHandler, DropHandler. Notice that
the action in the canImport() method is to simply count the
number of drops to the component.
import javax.swing.TransferHandler;
import javax.swing.JComponent;
import javax.swing.text.JTextComponent;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.DataFlavor;
class DropHandler extends TransferHandler {
private int numberOfDrops;
public boolean importData(JComponent component,
Transferable transferable){
JTextComponent source =
(JTextComponent) component;
source.setText(
"Drop Number: " + ++numberOfDrops);
return true;
}
public boolean canImport(JComponent c,
DataFlavor[] flavors) {
return true;
}
}
To run this example, you have to tie TransferHandler to the
target component. All JComponents have the ability to register
with a TransferHandler through the method setTransferHandler().
You can add this call to the createTextArea() method in Drop.java
like this:
private void createTextArea() {
jText = new JEditorPane();
getContentPane().add(jText, BorderLayout.CENTER);
jText.setTransferHandler(new DropHandler());
}
There are two important items to note before running this example.
First, when you override a component's default TransferHandler,
the default behaviors are no longer there. In this case, if you
drop a String on the JEditorPane you will not see the String
appear as you might expect. Second, because you have overridden
canImport() to always return true, your JEditorPane will accept
any drop. However, depending on where you drag from, you might
lose the data in the source component. For example, if you drag
from a text editor, the data is removed from that editor on a
successful drop.
Let's customize the TransferHandler further to only accept File
objects. To do this, you apply the ideas of DataFlavors
introduced in the March Tech Tip. The object being transferred
has an array of all the DataFlavor objects it supports. You can
test whether one of those supported DataFlavors is File. You do
that by modifying the canImport() method like this:
public boolean canImport(JComponent c,
DataFlavor[] flavors) {
return Arrays.asList(flavors).
contains(DataFlavor.javaFileListFlavor);
}
public void drop(DropTargetDropEvent dtde) {
Transferable transferable =
dtde.getTransferable();
if (transferable.isDataFlavorSupported(
DataFlavor.javaFileListFlavor)) {
dtde.acceptDrop(DnDConstants.ACTION_COPY);
List files = null;
try {
files =
(List) transferable.getTransferData(
DataFlavor.javaFileListFlavor);
} //exceptions omitted here
for (int i = 0; i < files.size(); i++) {
File file = (File) files.get(i);
jText.setText(
jText.getText() + "\n" + file);
}
}
}
You convert the array of DataFlavor objects into a List and ask
whether the list contains DataFlavor.javaFileListFlavor.
In order to handle the File being dropped on the JEditorPane,
you also need to modify the importData() method. Using the
method, you can test that the object being dropped is a File. If
it isn't a File, you return false. If it is a File, you try to
handle the file object. As always, you need to handle the
appropriate exceptions.
The actual code that handles the file is in the importFiles()
method. In the following example, an updated version of
DropHandler, the importFiles() method displays the paths of each
dropped File, one path on each line. The updated DropHandler looks
like this.
import javax.swing.TransferHandler;
import javax.swing.JComponent;
import javax.swing.text.JTextComponent;
import java.util.List;
import java.util.Arrays;
import java.io.File;
import java.io.IOException;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
class DropHandler extends TransferHandler {
public boolean importData(JComponent component,
Transferable transferable) {
if (!canImport(component,
transferable.getTransferDataFlavors())) {
return false;
} else {
JTextComponent source =
(JTextComponent) component;
try {
importFiles(transferable, source);
return true;
} catch (UnsupportedFlavorException e) {
source.setText(e.getMessage());
} catch (IOException e) {
source.setText(e.getMessage());
}
return false;
}
}
private void importFiles(Transferable transferable,
JTextComponent source)
throws UnsupportedFlavorException, IOException {
List files =
(List) transferable.getTransferData(
DataFlavor.javaFileListFlavor);
for (int i = 0; i < files.size(); i++) {
source.setText(source.getText() + "\n"
+ (File)files.get(i));
}
}
public boolean canImport(JComponent c,
DataFlavor[] flavors) {
return Arrays.asList(flavors).
contains(DataFlavor.javaFileListFlavor);
}
}
Now when you run Drop, the JEditorPane will only accept files.
You can drop as many files at one time as you like. You will see
their paths displayed, one path on each line.
The final version of the code is in response to James Gosling's
java.net blog entry "URLs are your friend".
In it he explained
that he prefers to convert to URLs when using drag and drop. In
this final example, instead of displaying the paths of the
dropped files in the JEditorPane, URLs are constructed from the
files and the URLs are then displayed.
Gosling also points out that some browsers and other applications
drop Strings when you might expect Files. This version of
DropHandler accepts Files or Strings by refactoring canImport(),
and introducing the following helper methods canImportFiles()
and canImportStrings():
public boolean canImport(JComponent c,
DataFlavor[] flavors) {
return canImportFiles(flavors) ||
canImportStrings(flavors);
}
private boolean canImportFiles(
DataFlavor[] flavors) {
return Arrays.asList(flavors).
contains(DataFlavor.javaFileListFlavor);
}
private boolean canImportStrings(
DataFlavor[] flavors) {
return Arrays.asList(flavors).
contains(DataFlavor.stringFlavor);
}
In the updated DropHandler below, the importFiles() method is
altered to display the URL instead of the path, and the
following importStrings() method is added:
private void importStrings(Transferable transferable,
JTextComponent source)
throws UnsupportedFlavorException, IOException {
String string =
(String) transferable.getTransferData(
DataFlavor.stringFlavor);
String message = null;
File file = new File(string);
if (file.exists()) {
message = file.toURL().toString();
} else {
try {
URL url = new URL(string);
url.getContent();
message = url.toString();
} catch (IOException e) {
message = "Could not convert string to URL "
+ string;
}
}
source.setText(source.getText() + "\n" + message);
}
The importStrings() method tries to construct a File from the
String. If the File exists, the method constructs the URL using
File's toURL() method. If the File does not exist, importStrings()
tries to construct a URL directly from the String. The method then
checks that there is a resource attached to the URL. If you drag
an image from a Web browser, the DropHandler should now behave
properly if it can. If it cannot, the DropHandler will report the
error.
Also, the importData() method can be simplified. You can use the
method to test if the transferred object is a File or a String,
and then call the appropriate method. Here is the revised
DropHandler.
import javax.swing.TransferHandler;
import javax.swing.JComponent;
import javax.swing.text.JTextComponent;
import java.util.List;
import java.util.Arrays;
import java.io.File;
import java.io.IOException;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.net.URL;
class DropHandler extends TransferHandler {
public boolean importData(JComponent component,
Transferable transferable) {
DataFlavor[] flavors =
transferable.getTransferDataFlavors();
JTextComponent source =
(JTextComponent) component;
try {
if (canImportFiles(flavors)) {
importFiles(transferable, source);
} else if (canImportStrings(flavors)) {
importStrings(transferable, source);
} else
return false;
return true;
} catch (UnsupportedFlavorException e) {
source.setText(e.getMessage());
} catch (IOException e) {
source.setText(e.getMessage());
}
return false;
}
private void importFiles(Transferable transferable,
JTextComponent source)
throws UnsupportedFlavorException, IOException {
List files =
(List) transferable.getTransferData(
DataFlavor.javaFileListFlavor);
for (int i = 0; i < files.size(); i++) {
source.setText(source.getText() + "\n" +
((File) (files.get(i))).toURL());
}
}
private void importStrings(
Transferable transferable,
JTextComponent source)
throws UnsupportedFlavorException, IOException {
String string =
(String) transferable.getTransferData(
DataFlavor.stringFlavor);
String message = null;
File file = new File(string);
if (file.exists()) {
message = file.toURL().toString();
} else {
try {
URL url = new URL(string);
url.getContent();
message = url.toString();
} catch (IOException e) {
message = "Could not convert string to URL "
+ string;
}
}
source.setText(
source.getText() + "\n" + message);
}
public boolean canImport(JComponent c,
DataFlavor[] flavors) {
return canImportFiles(flavors) ||
canImportStrings(flavors);
}
private boolean canImportFiles(
DataFlavor[] flavors) {
return Arrays.asList(flavors).
contains(DataFlavor.javaFileListFlavor);
}
private boolean canImportStrings(
DataFlavor[] flavors) {
return Arrays.asList(flavors).
contains(DataFlavor.stringFlavor);
}
}
This tip began with the default TransferHandler for a typical
JTextComponent. It then showed a TransferHandler that could take
any DataFlavor, but had limited functionality. Then it modified
the TransferHandler to accept Files or Strings, and attempted to
create useful URLs from this information. Both customization
examples showed that you adjust the canImport() and importData()
methods to meet your needs.
For more information about TransferHandler and data transfer in
general, see the trail
"How to Use Data Transfer"
in the Java Tutorial.
IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html
Comments? Send your feedback on the Core Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet
Subscribe to other Java developer Tech Tips:
- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the
Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2
Platform, Micro Edition (J2ME).
To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click
"Update".
- To unsubscribe, go to the subscriptions
page, uncheck the appropriate checkbox, and click "Update".
ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
Copyright 2003 Sun Microsystems, Inc. All
rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks (http://www.sun.com/suntrademarks/) of Sun
Microsystems, Inc. in the United States and other countries.
|