/* * MapDropTarget.java * * Created on August 13, 2006, 2:21 PM * */ package ika.gui; import ika.geo.*; import ika.geoimport.*; import ika.geoimport.DataReceiver; import java.awt.*; import java.awt.datatransfer.*; import java.awt.dnd.*; import java.awt.image.BufferedImage; import javax.swing.*; import java.io.*; import java.util.*; import java.lang.reflect.*; /** * MapDropTarget handles drag and drop for a MapComponent. * MapDropTarget contains a reference to a DataReceiver which handles * drop events of GeoObjects on a map and stores the passed GeoObjects. * Dropped GeoObjects are stored by MapDropTarget at the top level of the map, * unless a different DataReceiver is set by setDataReceiver(). * * @author Bernhard Jenny, Institute of Cartography, ETH Zurich */ public class MapDropTarget extends BorderedDropTarget { // a special data flavour for exchanging images on Macs. static DataFlavor macPictStreamFlavor; static { try { MapDropTarget.macPictStreamFlavor = new DataFlavor("image/x-pict; class=java.io.InputStream"); } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); } } /** * The DataReceiver handles drop events of data on a MapComponent. * It is responsible for storing the data at the right place. This object * is used in an importer thread to store the imported objects. */ private DataReceiver dataReceiver; /** Creates a new instance of MapDropTarget */ public MapDropTarget(MapComponent mapComponent) { super(mapComponent); this.dataReceiver = new MapDataReceiver(mapComponent); } /** * Returns a hash map of urls (key) and importers (value) for a * list of Files that is stored in a Transferable. */ private Hashtable fileImporters(Transferable trans) { try { java.util.List fileList = (java.util.List) trans.getTransferData(DataFlavor.javaFileListFlavor); return ika.geoimport.GeoImporter.findGeoImporters(fileList); } catch (Exception e) { return new Hashtable(0); } } /** * Handle a drop event: extract the data and call the MapDataReceiver to * store the data. */ @Override public void drop(DropTargetDropEvent dtde) { // call parent drop() to hide the border. super.drop(dtde); /* *From this method, the DropTargetListener shall accept or reject the drop via the acceptDrop(int dropAction) or rejectDrop() methods of the DropTargetDropEvent parameter. Subsequent to acceptDrop(), but not before, DropTargetDropEvent's getTransferable() method may be invoked, and data transfer may be performed via the returned Transferable's getTransferData() method. */ dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); Transferable trans = dtde.getTransferable(); boolean gotData = false; try { // treat a list of File objects if (trans.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { this.importFiles(dtde); gotData = true; } // try to get an image else if (trans.isDataFlavorSupported(DataFlavor.imageFlavor)) { this.importImage(dtde); gotData = true; } // try to get a Mac PICT using QuickTime. Only needed on Mac. else if (trans.isDataFlavorSupported(macPictStreamFlavor)) { this.importPICT(dtde); gotData = true; } } catch (Exception e) { e.printStackTrace(); } finally { /* *At the completion of a drop, an implementation of this method is required to signal the success/failure of the drop by passing an appropriate boolean to the DropTargetDropEvent's dropComplete(boolean success) method. Note: The data transfer should be completed before the call to the DropTargetDropEvent's dropComplete(boolean success) method. After that, a call to the getTransferData() method of the Transferable returned by DropTargetDropEvent.getTransferable() is guaranteed to succeed only if the data transfer is local; that is, only if DropTargetDropEvent.isLocalTransfer() returns true. Otherwise, the behavior of the call is implementation-dependent. */ dtde.dropComplete(gotData); } } /** * URLImporter reads a series of files using corresponding importers. */ private class URLImporter extends Thread { /** The progress indicator */ public ProgressPanel prog; /** A hash map with pairs of files and importers. */ public Hashtable url_importer_pairs; private String constructProgressMessage(java.net.URL url) { String fileName = ika.utils.FileUtils.getFileName(url.getPath()); return "Reading " + fileName + ". Please wait..."; } @Override public void run() { // import each data source sequentially Iterator iterator = url_importer_pairs.keySet().iterator(); while (iterator.hasNext()) { // get the URL final java.net.URL url = (java.net.URL)iterator.next(); // get the importer final GeoImporter importer = (GeoImporter)url_importer_pairs.get(url); // configure the progress dialog ika.utils.SwingThreadUtils.invokeAndWait(new Runnable() { public void run() { prog.setMessage(constructProgressMessage(url)); //importer.setProgressIndicator(prog); FIXME } }); // import the data in this same thread. importer.read(url, dataReceiver, GeoImporter.SAME_THREAD); } } } /** Reads data from a list of File objects. */ private void importFiles(DropTargetDropEvent dtde) throws Exception { // URLImporter is a local class defined above. URLImporter fileImporter = new URLImporter(); // Find importers for the files before the new thread is started! // The passed event is not valid anymore in the new thread. fileImporter.url_importer_pairs = fileImporters(dtde.getTransferable()); // setup progress dialog in Swing thread if (this.targetComponent != null) { JFrame owner = (JFrame)this.targetComponent.getTopLevelAncestor(); // fileImporter.prog = new ika.gui.ProgressPanel(owner, "Import", null, true); FIXME // start new thread that will sequentially read all files fileImporter.start(); } } /** Reads a Java Image object */ private void importImage(DropTargetDropEvent dtde) throws UnsupportedFlavorException, IOException { Transferable trans = dtde.getTransferable(); Image img = (Image) trans.getTransferData(DataFlavor.imageFlavor); GeoImage geoImage = this.imageToGeoImage(img, dtde.getLocation()); // store the image this.dataReceiver.add(geoImage); } /** Reads a Mac PICT using QuickTime. Only needed on Mac. */ private void importPICT(DropTargetDropEvent dtde) throws Exception { Transferable trans = dtde.getTransferable(); InputStream in = (InputStream) trans.getTransferData(macPictStreamFlavor); // for the benefit of the non-mac crowd, this is // done with reflection. directly, it would be: // Image img = QTJPictHelper.pictStreamToJavaImage (in); Class qtjphClass = Class.forName("ika.gui.QTJPictHelper"); Class[] methodParamTypes = { java.io.InputStream.class }; Method method = qtjphClass.getDeclaredMethod( "pictStreamToJavaImage", methodParamTypes); InputStream[] methodParams = { in }; // create a GeoImage Image img = (Image) method.invoke(null, (Object[])methodParams); GeoImage geoImage = this.imageToGeoImage(img, dtde.getLocation()); // store the image this.dataReceiver.add(geoImage); } /** Creates a GeoImage from an Image. Takes care of the position and size * of the new GeoImage. */ private GeoImage imageToGeoImage(Image img, Point location) { if (img == null || location == null) return null; // convert image to GeoImage BufferedImage bImg = ika.utils.ImageUtils.makeBufferedImage(img); MapComponent mapComponent = (MapComponent)this.targetComponent; final java.awt.geom.Point2D.Double pt; pt = mapComponent.userToWorldSpace(location); // scale and place image final double pixelSize = 1. / mapComponent.getScaleFactor(); return new GeoImage(bImg, pt.getX(), pt.getY(), pixelSize); } /** Utility method for debugging. */ 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]); } } /** * Getter for the DataReceiver, which stores dropped GeoObjects. */ public DataReceiver getDataReceiver() { return dataReceiver; } /** * Setter for the DataReceiver, which stores dropped GeoObjects. */ public void setDataReceiver(DataReceiver mapDataReceiver) { this.dataReceiver = mapDataReceiver; } }