/* * @(#)DefaultDrawingViewTransferHandler.java * * Copyright (c) 2007-2010 The authors and contributors of JHotDraw. * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. */ package org.jhotdraw.draw; import javax.annotation.Nullable; import org.jhotdraw.draw.io.InputFormat; import org.jhotdraw.draw.io.OutputFormat; import org.jhotdraw.draw.event.CompositeFigureEvent; import org.jhotdraw.draw.event.CompositeFigureListener; import java.awt.*; import java.awt.datatransfer.*; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DragGestureRecognizer; import java.awt.dnd.DragSource; import java.awt.dnd.DragSourceContext; import java.awt.dnd.DragSourceDragEvent; import java.awt.dnd.DragSourceDropEvent; import java.awt.dnd.DragSourceEvent; import java.awt.dnd.DragSourceListener; import java.awt.event.*; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.undo.*; import org.jhotdraw.gui.Worker; import org.jhotdraw.gui.datatransfer.*; import org.jhotdraw.util.ResourceBundleUtil; import org.jhotdraw.util.ReversedList; /** * Default TransferHandler for DrawingView objects. * * @author Werner Randelshofer * @version $Id: DefaultDrawingViewTransferHandler.java 717 2010-11-21 12:30:57Z * rawcoder $ */ public class DefaultDrawingViewTransferHandler extends TransferHandler { private static final long serialVersionUID = 1L; private static final boolean DEBUG = false; /** * We keep the exported figures in this list, so that we don't need to rely * on figure selection, when method exportDone is called. */ @Nullable private HashSet<Figure> exportedFigures; /** * Creates a new instance. */ public DefaultDrawingViewTransferHandler() { } @Override public boolean importData(JComponent comp, Transferable t) { return importData(comp, t, new HashSet<>(), null); } @Override public boolean importData(TransferSupport support) { return importData((JComponent) support.getComponent(), support.getTransferable(), new HashSet<>(), support.getDropLocation() == null ? null : support.getDropLocation().getDropPoint()); } /** * Imports data and stores the transferred figures into the supplied * transferFigures collection. */ @SuppressWarnings("unchecked") protected boolean importData(final JComponent comp, Transferable t, final HashSet<Figure> transferFigures, @Nullable final Point dropPoint) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler.importData(comp,t)"); } boolean retValue; if (comp instanceof DrawingView) { final DrawingView view = (DrawingView) comp; final Drawing drawing = view.getDrawing(); if (drawing.getInputFormats() == null || drawing.getInputFormats().size() == 0) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler import failed; drawing has no import formats"); } retValue = false; } else { retValue = false; try { DataFlavor[] transferFlavors = t.getTransferDataFlavors(); // Workaround for Mac OS X: // The Apple JVM messes up the sequence of the data flavors. if (System.getProperty("os.name").toLowerCase().startsWith("mac")) { // Search for a suitable input format SearchLoop: for (InputFormat format : drawing.getInputFormats()) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler trying format:" + format); } for (DataFlavor flavor : transferFlavors) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler trying flavor:" + flavor.getMimeType()); } if (format.isDataFlavorSupported(flavor)) { LinkedList<Figure> existingFigures = new LinkedList<>(drawing.getChildren()); try { format.read(t, drawing, false); if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler import succeeded"); } final LinkedList<Figure> importedFigures = new LinkedList<>(drawing.getChildren()); importedFigures.removeAll(existingFigures); view.clearSelection(); view.addToSelection(importedFigures); transferFigures.addAll(importedFigures); moveToDropPoint(comp, transferFigures, dropPoint); drawing.fireUndoableEditHappened(new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; @Override public String getPresentationName() { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); return labels.getString("edit.paste.text"); } @Override public void undo() throws CannotUndoException { super.undo(); drawing.removeAll(importedFigures); } @Override public void redo() throws CannotRedoException { super.redo(); drawing.addAll(importedFigures); } }); retValue = true; break SearchLoop; } catch (IOException e) { if (DEBUG) { System.out.println(" import failed"); e.printStackTrace(); } // failed to read transferalbe, try with next InputFormat } } } } } else { // Search for a suitable input format SearchLoop: for (DataFlavor flavor : transferFlavors) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler trying flavor:" + flavor.getMimeType()); } for (InputFormat format : drawing.getInputFormats()) { if (format.isDataFlavorSupported(flavor)) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler trying format:" + format); } LinkedList<Figure> existingFigures = new LinkedList<>(drawing.getChildren()); try { format.read(t, drawing, false); if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler import succeeded"); } final LinkedList<Figure> importedFigures = new LinkedList<>(drawing.getChildren()); importedFigures.removeAll(existingFigures); view.clearSelection(); view.addToSelection(importedFigures); transferFigures.addAll(importedFigures); moveToDropPoint(comp, transferFigures, dropPoint); drawing.fireUndoableEditHappened(new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; @Override public String getPresentationName() { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); return labels.getString("edit.paste.text"); } @Override public void undo() throws CannotUndoException { super.undo(); drawing.removeAll(importedFigures); } @Override public void redo() throws CannotRedoException { super.redo(); drawing.addAll(importedFigures); } }); retValue = true; break SearchLoop; } catch (IOException e) { if (DEBUG) { System.out.println(" import failed"); e.printStackTrace(); } // failed to read transferalbe, try with next InputFormat } } } } } // No input format found? Lets see if we got files - we // can handle these if (retValue == false && t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { final java.util.List<File> files = (java.util.List<File>) t.getTransferData(DataFlavor.javaFileListFlavor); retValue = true; final LinkedList<Figure> existingFigures = new LinkedList<>(drawing.getChildren()); view.getEditor().setEnabled(false); // FIXME - We should perform the following code in a // worker thread. new Worker<LinkedList<Figure>>() { @Override public LinkedList<Figure> construct() throws Exception { for (File file : files) { FileFormatLoop: for (InputFormat format : drawing.getInputFormats()) { if (file.isFile() && format.getFileFilter().accept(file)) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler importing file " + file); } format.read(file.toURI(), drawing, false); } } } return new LinkedList<>(drawing.getChildren()); } @Override public void failed(Throwable error) { error.printStackTrace(); } @Override public void done(final LinkedList<Figure> importedFigures) { importedFigures.removeAll(existingFigures); if (importedFigures.size() > 0) { view.clearSelection(); view.addToSelection(importedFigures); transferFigures.addAll(importedFigures); moveToDropPoint(comp, transferFigures, dropPoint); drawing.fireUndoableEditHappened(new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; @Override public String getPresentationName() { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); return labels.getString("edit.paste.text"); } @Override public void undo() throws CannotUndoException { super.undo(); drawing.removeAll(importedFigures); } @Override public void redo() throws CannotRedoException { super.redo(); drawing.addAll(importedFigures); } }); } } @Override public void finished() { view.getEditor().setEnabled(true); } }.start(); } } catch (Throwable e) { if (DEBUG) { e.printStackTrace(); } } } } else { retValue = super.importData(comp, t); } if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler .importData(comp,t):" + retValue); } return retValue; } protected void moveToDropPoint(JComponent component, HashSet<Figure> transferFigures, Point dropPoint) { if (dropPoint == null) { // This ugly code sequence is needed to ensure that the drawing view // repaints the area which contains the dropped figures. for (Figure fig : transferFigures) { fig.willChange(); fig.changed(); } } else { final DrawingView view = (DrawingView) component; Point2D.Double drawingDropPoint = view.viewToDrawing(dropPoint); //Set<Figure> transferFigures = view.getSelectedFigures(); Rectangle2D.Double drawingArea = null; for (Figure fig : transferFigures) { if (drawingArea == null) { drawingArea = fig.getDrawingArea(); } else { drawingArea.add(fig.getDrawingArea()); } } if (drawingArea != null) { AffineTransform t = new AffineTransform(); t.translate(-drawingArea.x, -drawingArea.y); t.translate(drawingDropPoint.x, drawingDropPoint.y); // XXX - instead of centering, we have to translate by the drag image offset here t.translate(drawingArea.width / -2d, drawingArea.height / -2d); for (Figure fig : transferFigures) { fig.willChange(); fig.transform(t); fig.changed(); } } } } @Override public int getSourceActions(JComponent c) { int retValue; if (c instanceof DrawingView) { DrawingView view = (DrawingView) c; if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler .getSourceActions outputFormats.size=" + view.getDrawing().getOutputFormats().size()); } retValue = (view.getDrawing().getOutputFormats().size() > 0 && view.getSelectionCount() > 0) ? COPY | MOVE : NONE; } else { retValue = super.getSourceActions(c); } if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler .getSourceActions:" + retValue); } return retValue; } @Override protected Transferable createTransferable(JComponent c) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler .createTransferable(" + c + ")"); } Transferable retValue; if (c instanceof DrawingView) { DrawingView view = (DrawingView) c; retValue = createTransferable(view, view.getSelectedFigures()); } else { retValue = super.createTransferable(c); } return retValue; } @Nullable protected Transferable createTransferable(DrawingView view, java.util.Set<Figure> transferFigures) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler .createTransferable(" + view + "," + transferFigures + ")"); } Transferable retValue; Drawing drawing = view.getDrawing(); exportedFigures = null; if (drawing.getOutputFormats() == null || drawing.getOutputFormats().size() == 0) { retValue = null; } else { java.util.List<Figure> toBeCopied = drawing.sort(transferFigures); if (toBeCopied.size() > 0) { try { CompositeTransferable transfer = new CompositeTransferable(); for (OutputFormat format : drawing.getOutputFormats()) { Transferable t = format.createTransferable( drawing, toBeCopied, view.getScaleFactor()); if (!transfer.isDataFlavorSupported(t.getTransferDataFlavors()[0])) { transfer.add(t); } } exportedFigures = new HashSet<>(transferFigures); retValue = transfer; } catch (IOException e) { if (DEBUG) { e.printStackTrace(); } retValue = null; } } else { retValue = null; } } return retValue; } @Override protected void exportDone(JComponent source, Transferable data, int action) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler .exportDone " + action + " move=" + MOVE); } if (source instanceof DrawingView) { final DrawingView view = (DrawingView) source; final Drawing drawing = view.getDrawing(); if (action == MOVE) { final LinkedList<CompositeFigureEvent> deletionEvents = new LinkedList<>(); final LinkedList<Figure> selectedFigures = (exportedFigures == null) ? // new LinkedList<>() : // new LinkedList<>(exportedFigures); // Abort, if not all of the selected figures may be removed from the // drawing for (Figure f : selectedFigures) { if (!f.isRemovable()) { source.getToolkit().beep(); return; } } // view.clearSelection(); CompositeFigureListener removeListener = new CompositeFigureListener() { @Override public void figureAdded(CompositeFigureEvent e) { } @Override public void figureRemoved(CompositeFigureEvent evt) { deletionEvents.addFirst(evt); } }; drawing.addCompositeFigureListener(removeListener); drawing.removeAll(selectedFigures); drawing.removeCompositeFigureListener(removeListener); drawing.removeAll(selectedFigures); drawing.fireUndoableEditHappened(new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; @Override public String getPresentationName() { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); return labels.getString("edit.delete.text"); } @Override public void undo() throws CannotUndoException { super.undo(); view.clearSelection(); for (CompositeFigureEvent evt : deletionEvents) { drawing.add(evt.getIndex(), evt.getChildFigure()); } view.addToSelection(selectedFigures); } @Override public void redo() throws CannotRedoException { super.redo(); for (CompositeFigureEvent evt : new ReversedList<>(deletionEvents)) { drawing.remove(evt.getChildFigure()); } } }); } } else { super.exportDone(source, data, action); } exportedFigures = null; } @Override public void exportAsDrag(JComponent comp, InputEvent e, int action) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler .exportAsDrag"); } if (comp instanceof DrawingView) { DrawingView view = (DrawingView) comp; HashSet<Figure> transferFigures = new HashSet<>(); MouseEvent me = (MouseEvent) e; Figure f = view.findFigure(me.getPoint()); if (view.getSelectedFigures().contains(f)) { transferFigures.addAll(view.getSelectedFigures()); } else { transferFigures.add(f); } Rectangle2D.Double drawingArea = null; for (Figure fig : transferFigures) { if (drawingArea == null) { drawingArea = fig.getDrawingArea(); } else { drawingArea.add(fig.getDrawingArea()); } } Rectangle viewArea = view.drawingToView(drawingArea); Point imageOffset = me.getPoint(); imageOffset.x = viewArea.x - imageOffset.x; imageOffset.y = viewArea.y - imageOffset.y; int srcActions = getSourceActions(comp); SwingDragGestureRecognizer recognizer = new SwingDragGestureRecognizer(new DragHandler( createTransferable(view, transferFigures), imageOffset)); recognizer.gestured(comp, me, srcActions, action); // XXX - What kind of drag gesture can we support for this?? } else { super.exportAsDrag(comp, e, action); } } @Override public Icon getVisualRepresentation( Transferable t) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler .getVisualRepresentation"); } Image image = null; try { image = (Image) t.getTransferData(DataFlavor.imageFlavor); } catch (IOException | UnsupportedFlavorException ex) { if (DEBUG) { ex.printStackTrace(); } } return (image == null) ? null : new ImageIcon(image); } @Override public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { if (DEBUG) { System.out.println("DefaultDrawingViewTransferHandler .canImport " + Arrays.asList(transferFlavors)); } boolean retValue; if (comp instanceof DrawingView) { DrawingView view = (DrawingView) comp; Drawing drawing = view.getDrawing(); // Search for a suitable input format retValue = false; SearchLoop: for (InputFormat format : drawing.getInputFormats()) { for (DataFlavor flavor : transferFlavors) { if (flavor.isFlavorJavaFileListType() || format.isDataFlavorSupported(flavor)) { retValue = true; break SearchLoop; } } } } else { retValue = super.canImport(comp, transferFlavors); } return retValue; } private void getDrawing() { throw new UnsupportedOperationException("Not yet implemented"); } /** * This is the default drag handler for drag and drop operations that use * the * <code>TransferHandler</code>. */ private static class DragHandler implements DragGestureListener, DragSourceListener { private boolean scrolls; private Transferable transferable; private Point imageOffset; public DragHandler(Transferable t, Point imageOffset) { transferable = t; this.imageOffset = imageOffset; } // --- DragGestureListener methods ----------------------------------- /** * a Drag gesture has been recognized */ @Override public void dragGestureRecognized(DragGestureEvent dge) { JComponent c = (JComponent) dge.getComponent(); DefaultDrawingViewTransferHandler th = (DefaultDrawingViewTransferHandler) c.getTransferHandler(); Transferable t = transferable; if (t != null) { scrolls = c.getAutoscrolls(); c.setAutoscrolls(false); try { // dge.startDrag(null, t, this); Icon icon = th.getVisualRepresentation(t); Image dragImage; if (icon instanceof ImageIcon) { dragImage = ((ImageIcon) icon).getImage(); } else { dragImage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); Graphics g = ((BufferedImage) dragImage).createGraphics(); icon.paintIcon(c, g, 0, 0); g.dispose(); } dge.startDrag(null, dragImage, imageOffset, t, this); return; } catch (RuntimeException re) { c.setAutoscrolls(scrolls); } } th.exportDone(c, t, NONE); } // --- DragSourceListener methods ----------------------------------- /** * as the hotspot enters a platform dependent drop site */ @Override public void dragEnter(DragSourceDragEvent dsde) { } /** * as the hotspot moves over a platform dependent drop site */ @Override public void dragOver(DragSourceDragEvent dsde) { } /** * as the hotspot exits a platform dependent drop site */ @Override public void dragExit(DragSourceEvent dsde) { } /** * as the operation completes */ @Override public void dragDropEnd(DragSourceDropEvent dsde) { DragSourceContext dsc = dsde.getDragSourceContext(); JComponent c = (JComponent) dsc.getComponent(); DefaultDrawingViewTransferHandler th = (DefaultDrawingViewTransferHandler) c.getTransferHandler(); if (dsde.getDropSuccess()) { th.exportDone(c, dsc.getTransferable(), dsde.getDropAction()); } else { th.exportDone(c, dsc.getTransferable(), NONE); } c.setAutoscrolls(scrolls); } @Override public void dropActionChanged(DragSourceDragEvent dsde) { } } private static class SwingDragGestureRecognizer extends DragGestureRecognizer { private static final long serialVersionUID = 1L; SwingDragGestureRecognizer(DragGestureListener dgl) { super(DragSource.getDefaultDragSource(), null, NONE, dgl); } void gestured(JComponent c, MouseEvent e, int srcActions, int action) { setComponent(c); setSourceActions(srcActions); appendEvent(e); fireDragGestureRecognized(action, e.getPoint()); } /** * register this DragGestureRecognizer's Listeners with the Component */ @Override protected void registerListeners() { } /** * unregister this DragGestureRecognizer's Listeners with the Component * * subclasses must override this method */ @Override protected void unregisterListeners() { } } }