Core Java Technologies Tech Tips Tips, Techniques, and Sample Code 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 (http://java.net). You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/JDCTechTips/2003/tt1014.html. 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 (http://java.sun.com/jdc/JDCTechTips/2003/tt0909.html#2) 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 (http://java.sun.com/javase/technologies/desktop/java3d/). 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: o Create a Canvas3D object. o Create a scene graph. o 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); } } 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. 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. 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 (http://java.sun.com/jdc/onlineTraining/java3d/). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING THE TRANSFERHANDLER The March 18, 2003 Tech Tip "Dragging Text and Images with Swing" (http://java.sun.com/jdc/JDCTechTips/2003/tt0318.html#1) 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" (http://today.java.net/jag/page4.html#37 ). 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" (http://java.sun.com/docs/books/tutorial/uiswing/misc/dnd.html) 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 * FEEDBACK Comments? Please enter your feedback on the Tech Tips at: http://developers.sun.com/contact/feedback.jsp?category=sdn * SUBSCRIBE/UNSUBSCRIBE 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, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), choose the newsletters you want to subscribe to and click "Update". - To unsubscribe, go to the subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), uncheck the appropriate checkbox, and click "Update". - To use our one-click unsubscribe facility, see the link at the end of this email: - ARCHIVES You'll find the Core Java Technologies Tech Tips archives at: http://java.sun.com/jdc/TechTips/index.html - COPYRIGHT Copyright 2003 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, California 95054 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html Core Java Technologies Tech Tips October 14, 2003 Trademark Information: http://www.sun.com/suntrademarks/ Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.