Handle Dropped Images





Handle Dropped Images

I spy, with my little drop( ) method, something that doesn't support DataFlavor.imageFlavor…arrrgh!

Handling drag-and-drop from native applications [Hack #66] can be tricky because they're not particularly consistent about how they represent the data being transferred to your application. Now, let's say you want to accept dropped imagesnot image files, but actual images inside of browser windows, digital photo viewers, word processing and page-layout applications, etc. There's a constant in DataFlavor for images, so surely you can count on that being a flavor offered to you by the TRansferable, right?

No, of course not. That would be too easy.

Using the dumpDataFlavors( ) strategy of the earlier URL hack, I checked out the DataFlavors offered by images dropped from some popular Windows and Mac applications. The results are pretty interestingcheck out Figure.

DataFlavor offerings for images on various platforms

Application/platform

DataFlavors

Preview 2.1 / Mac OS X

1

GraphicConverter 4.6 / Mac OS X

1

Finder / Mac OS X

1

Safari 1.3 / Mac OS X

55

Firefox 1.0 / Mac OS X

57

QuickTime Player 6.5 / Mac OS X

1

AppleWorks 6.2.9 / Mac OS X

1

MarinerWrite 3.6.4 / Mac OS X

1

iPhoto 4.0.3 / Mac OS X

1

Explorer / Windows XP

1

MSIE 6.0 / Windows XP

27

Firefox 1.0 / Windows XP

80

Paint / Windows XP

N/A

Windows Picture and Fax Viewer / Windows XP

N/A

Windows Media Player / Windows XP

N/A

QuickTime Player 6.5.1 / Windows XP

N/A

QuickTime Picture Viewer 6.5.1 / Windows XP

N/A


The first thing you should notice is the poor support for dragging and dropping images in Windows: Paint, Picture and Fax Viewer, and the Quick-Time applications are not drag sources, and dragging an image from the playlist of Windows Media Player to the Swing application in this hack actually crashes Java.

Furthermore, the supported DataFlavors are all over the map. A lot of these provide references to image files in the form of either a javaFileListFlavor (as do the Windows and Mac desktops), a list of URIs (the MIME type text/uri-list defined by RFC 2483 and supported by many of the browsers), or a single URL. To top it off, some of the older Mac applications send a single, hard-to-handle DataFlavor that will require special QuickTime-based handling [Hack #68].

Thanks to Swing's support for common image types like GIF, JPEG, and PNG, a file reference is good enough because you can easily make an ImageIcon from a URL or filepath to any of these types.

Grabbing the Drop

Figure shows a basic application that accepts a drop and tries to obtain from the drop (in order of preference):

  1. A java.awt.Image

  2. A java.util.List of java.io.Files

  3. A String, formatted per the uri-list spec

  4. A java.net.URL

Handling dropped images from other applications
	public class ImageDropTargetDemo extends JPanel 
		implements DropTargetListener {
		DropTarget dropTarget;
		JLabel dropHereLabel;
		static DataFlavor urlFlavor, uriListFlavor, macPictStreamFlavor;
		static {

			try { 
				urlFlavor = 
				new DataFlavor ("application/x-java-url; class=java.net.URL"); 
				uriListFlavor = 
				new DataFlavor ("text/uri-list; class=java.lang.String");
			} catch (ClassNotFoundException cnfe) { 
				cnfe.printStackTrace( );
			}
		}

		public ImageDropTargetDemo( )  {
			super(new BorderLayout( ));
			dropHereLabel = new JLabel (" Drop here ",
			
							SwingConstants.CENTER);    
			dropHereLabel.setFont (getFont( ).deriveFont (Font.BOLD, 24.0f)); 
			add (dropHereLabel, BorderLayout.CENTER); 
			// set up drop target stuff
			dropTarget = new DropTarget (dropHereLabel, this);
		}
		
		public static void main (String[] args) { 
			JFrame frame = new JFrame ("Image DropTarget Demo");
			ImageDropTargetDemo demoPanel = new ImageDropTargetDemo( ); 
			frame.getContentPane( ).add (demoPanel); 
			frame.pack( ); frame.setVisible(true);
		}
		
		// drop target listener events
		
		public void dragEnter (DropTargetDragEvent dtde) {}
		
		public void dragExit (DropTargetEvent dte) {}
		
		public void dragOver (DropTargetDragEvent dtde) {}
		
		// drop( ) method listed below
		
		public void dropActionChanged (DropTargetDragEvent dtde) {}
		
		public void showImageInNewFrame (ImageIcon icon) {
			JFrame frame = new JFrame( );
			frame.getContentPane( ).add (new JLabel (icon));
			frame.pack( );
			frame.setVisible(true);
		}
		public void showImageInNewFrame (Image image) {
			showImageInNewFrame (new ImageIcon (image));
		}

		private void dumpDataFlavors (Transferable trans) {
			System.out.println ("Flavors:");
			DataFlavor[] flavors = trans.getTransferDataFlavors( );
			for (int i=0; i<flavors.length; i++) {
				System.out.println ("*** " + i + ": " + flavors[i]);
			}
		}
	}

As in "Handle Dropped URLs" [Hack #66], this program uses static initializers to set up two custom DataFlavors: one for URLs and another for lists of URIs provided as Strings. Constants for images (specifically, java.awt.Images) and lists of files (java.util.Lists of java.io.Files)are provided for you by the DataFlavor class.

You register for drag-and-drop by creating a DropTarget with an onscreen component and an implementation of the DropTargetListener class. The key to the DropTargetListener is to meaningfully implement the drop( ) method, taking a DropTargetDropEvent and accepting the drop, trying to get the data from the event's transferable, and reporting back to the event on whether the data was handled successfully. To prove that it worked, this application shows the dropped image in a new JFrame.

Of course, the devil is in the details, meaning the drop( ) method, which is listed in Figure.

Handling the image drop
	public void drop (DropTargetDropEvent dtde) { 
		System.out.println ("drop");
		dtde.acceptDrop (DnDConstants.ACTION_COPY_OR_MOVE);   
		Transferable trans = dtde.getTransferable( );
		System.out.println ("Flavors:"); 
		dumpDataFlavors (trans); boolean gotData = false;
		try {
			// try to get an image
			if (trans.isDataFlavorSupported (DataFlavor.imageFlavor)) { 
				System.out.println ("image flavor is supported"); 
				Image img = (Image) trans.getTransferData (DataFlavor.imageFlavor); 
				showImageInNewFrame (img); 
				gotData = true;
			} else if (trans.isDataFlavorSupported (
				DataFlavor.javaFileListFlavor)) {
				System.out.println ("javaFileList is supported");
				java.util.List list = (java.util.List)
				trans.getTransferData (DataFlavor.javaFileListFlavor);
				ListIterator it = list.listIterator( );    
				while (it.hasNext( )) {
				File f = (File) it.next( );
				ImageIcon icon = new ImageIcon (f.getAbsolutePath( ));
				showImageInNewFrame (icon);
				}
				gotData = true;
			} else if (trans.isDataFlavorSupported (uriListFlavor)) {
				System.out.println ("uri-list flavor is supported"); 
				String uris = (String)
				trans.getTransferData (uriListFlavor);

				// url-lists are defined by rfc 2483 as crlf-delimited 
				StringTokenizer izer = new StringTokenizer (uris, "\r\n");   
				while (izer.hasMoreTokens ( )) {
				String uri = izer.nextToken( );
				System.out.println (uri);
				ImageIcon icon = new ImageIcon (uri);
				showImageInNewFrame (icon);
				}
				gotData = true;
			} else if (trans.isDataFlavorSupported (urlFlavor)) {
				System.out.println ("url flavor is supported");
				URL url = (URL) trans.getTransferData (urlFlavor);
				System.out.println (url.toString( ));
				ImageIcon icon = new ImageIcon (url);
				showImageInNewFrame (icon);
				gotData = true;
			}
		} catch (Exception e) {
			e.printStackTrace( );
		} finally {
			System.out.println ("gotData is " + gotData);
			dtde.dropComplete (gotData);
		}
	}

drop( ) asks the transferable for supported DataFlavors in the order you'd prefer to deal with them. First, obviously, is DataFlavor.imageFlavor. If this is supported, you can trivially cast the data object returned by transferable. getTransferData( ) to an Image.

The next easiest thing to deal with is the Java file list, denoted by the MIME type application/x-java-file-list, and handled by the DataFlavor constant javaFileListFlavor. Given Swing's support for various image file formats, you can cast the transfer data to a java.util.List, iterate over its members, cast each one to a File, and make an ImageIcon from each file's path. URLs work pretty much the same way: cast the transfer data to a java.net.URL and make an ImageIcon from it.

URI lists take just a little work on your part. RFC 2483 defines these as being some number of URIs, separated by CRLF pairs. It's trivial to take the URI list as a string and send it to a StringTokenizer to pick out each URI, which can then be passed to the ImageIcon constructor.

Shut Up and Drag

Start up the demo and drag over an image from your favorite application, as shown in Figure.

Dragging an image from a native application to a Swing component


When dropped, you'll see the image appear in its own JFrame.In Figure, I've dragged small images from several different applications, resulting in each being opened in its own frame.

Image dropped from a native application and opened in Swing


This won't work if you're dragging an image from one of the Mac OS X applications that only supplies one DataFlavor. You'll have to deal with Picts [Hack #68] in that situation.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows