/*******************************************************************************
* Copyright (c) 2006-2012
* Software Technology Group, Dresden University of Technology
* DevBoost GmbH, Berlin, Amtsgericht Charlottenburg, HRB 140026
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Software Technology Group - TU Dresden, Germany;
* DevBoost GmbH - Berlin, Germany
* - initial API and implementation
******************************************************************************/
/*
* @(#)DefaultDrawingViewTransferHandler.java 2.0 2009-03-13
*
* Copyright (c) 2007-2009 by the original authors of JHotDraw
* and all its contributors.
* All rights reserved.
*
* The copyright of this software is owned by the authors and
* contributors of the JHotDraw project ("the copyright holders").
* You may not use, copy or modify this software, except in
* accordance with the license agreement you entered into with
* the copyright holders. For details see accompanying license terms.
*/
package org.jhotdraw.draw;
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.
* <p>
* Note: This class is here for backwards compatibilty with J2SE 5. If you
* have J2SE 6 available, you may want to use class
* {@link DnDDrawingViewTransferHandler} instead.
*
* @author Werner Randelshofer
* @version 2.0 2009-03-13 Load drawings from files using a worker thread.
* <br>1.2 2008-05-24 Adapted to changes in InputFormat. Add support for
* automatically grouping
* <br>1.1.2 2008-03-20 After import, only select imported figures in
* drawing view.
* <br>1.1.1 2008-03-10 In method importData, figures are added to drawing
* by the InputFormat.
* <br>1.1 2007-12-16 Adapted to changes in InputFormat and OutputFormat.
* <br>1.0 April 13, 2007 Created.
*/
public class DefaultDrawingViewTransferHandler extends TransferHandler {
private final static 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.
*/
private HashSet<Figure> exportedFigures;
/** Creates a new instance. */
public DefaultDrawingViewTransferHandler() {
}
@Override
public boolean importData(JComponent comp, Transferable t) {
return importData(comp, t, new HashSet<Figure>());
}
/** Imports data and stores the transferred figures into the supplied transferFigures collection. */
@SuppressWarnings("unchecked")
protected boolean importData(JComponent comp, Transferable t, HashSet<Figure> transferFigures) {
if (DEBUG) {
System.out.println(this + ".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(this + ".importData failed - drawing has no import formats");
}
retValue = false;
} else {
retValue = false;
try {
// Search for a suitable input format
SearchLoop:
for (InputFormat format : drawing.getInputFormats()) {
for (DataFlavor flavor : t.getTransferDataFlavors()) {
if (DEBUG) {
System.out.println(this + ".importData trying to match " + format + " to flavor " + flavor);
}
if (format.isDataFlavorSupported(flavor)) {
if (DEBUG) {
System.out.println(this + ".importData importing flavor " + flavor);
}
LinkedList<Figure> existingFigures = new LinkedList<Figure>(drawing.getChildren());
format.read(t, drawing, false);
final LinkedList<Figure> importedFigures = new LinkedList<Figure>(drawing.getChildren());
importedFigures.removeAll(existingFigures);
view.clearSelection();
view.addToSelection(importedFigures);
transferFigures.addAll(importedFigures);
drawing.fireUndoableEditHappened(new AbstractUndoableEdit() {
public String getPresentationName() {
ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels");
return labels.getString("edit.paste.text");
}
public void undo() throws CannotUndoException {
super.undo();
drawing.removeAll(importedFigures);
}
public void redo() throws CannotRedoException {
super.redo();
drawing.addAll(importedFigures);
}
});
retValue = true;
break SearchLoop;
}
}
}
// 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<Figure>(drawing.getChildren());
view.getEditor().setEnabled(false);
// FIXME - We should perform the following code in a
// worker thread.
new Worker() {
@Override
public Object construct() {
try {
for (File file : files) {
FileFormatLoop:
for (InputFormat format : drawing.getInputFormats()) {
if (file.isFile() &&
format.getFileFilter().accept(file)) {
if (DEBUG) {
System.out.println(this + ".importData importing file " + file);
}
format.read(file, drawing, false);
}
}
}
return new LinkedList<Figure>(drawing.getChildren());
} catch (Throwable t) {
return t;
}
}
@Override
public void finished(Object value) {
if (value instanceof Throwable) {
((Throwable) value).printStackTrace();
} else {
final LinkedList<Figure> importedFigures = (LinkedList<Figure>) value;
importedFigures.removeAll(existingFigures);
if (importedFigures.size() > 0) {
view.clearSelection();
view.addToSelection(importedFigures);
drawing.fireUndoableEditHappened(new AbstractUndoableEdit() {
@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);
}
});
}
}
view.getEditor().setEnabled(true);
}
}.start();
}
} catch (Throwable e) {
if (DEBUG) {
e.printStackTrace();
}
}
}
} else {
retValue = super.importData(comp, t);
}
return retValue;
}
@Override
public int getSourceActions(JComponent c) {
int retValue;
if (c instanceof DrawingView) {
DrawingView view = (DrawingView) c;
if (DEBUG) {
System.out.println(this + ".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(this + ".getSourceActions:" + retValue);
}
return retValue;
}
@Override
protected Transferable createTransferable(JComponent c) {
if (DEBUG) {
System.out.println(this + ".createTransferable");
}
Transferable retValue;
if (c instanceof DrawingView) {
DrawingView view = (DrawingView) c;
retValue = createTransferable(view, view.getSelectedFigures());
} else {
retValue = super.createTransferable(c);
}
return retValue;
}
protected Transferable createTransferable(DrawingView view, java.util.Set<Figure> transferFigures) {
if (DEBUG) {
System.out.println(this + ".createTransferable");
}
Transferable retValue;
Drawing drawing = view.getDrawing();
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);
}
}
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(this + ".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<CompositeFigureEvent>();
// final LinkedList<Figure> selectedFigures = new LinkedList<Figure>(view.getSelectedFigures());
final LinkedList<Figure> selectedFigures = new LinkedList<Figure>(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() {
public void areaInvalidated(CompositeFigureEvent e) {
}
public void figureAdded(CompositeFigureEvent e) {
}
public void figureRemoved(CompositeFigureEvent evt) {
deletionEvents.addFirst(evt);
}
};
drawing.addCompositeFigureListener(removeListener);
drawing.removeAll(selectedFigures);
drawing.removeCompositeFigureListener(removeListener);
drawing.removeAll(selectedFigures);
drawing.fireUndoableEditHappened(new AbstractUndoableEdit() {
@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<CompositeFigureEvent>(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(this + ".exportAsDrag");
}
if (comp instanceof DrawingView) {
DrawingView view = (DrawingView) comp;
HashSet<Figure> transferFigures = new HashSet<Figure>();
exportedFigures = transferFigures;
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(this + ".getVisualRepresentation");
}
Image image = null;
try {
image = (Image) t.getTransferData(DataFlavor.imageFlavor);
} catch (IOException ex) {
if (DEBUG) {
ex.printStackTrace();
}
} catch (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(this + ".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
*/
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
*/
public void dragEnter(DragSourceDragEvent dsde) {
}
/**
* as the hotspot moves over a platform dependent drop site
*/
public void dragOver(DragSourceDragEvent dsde) {
}
/**
* as the hotspot exits a platform dependent drop site
*/
public void dragExit(DragSourceEvent dsde) {
}
/**
* as the operation completes
*/
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);
}
public void dropActionChanged(DragSourceDragEvent dsde) {
}
}
private static class SwingDragGestureRecognizer extends DragGestureRecognizer {
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
*/
protected void registerListeners() {
}
/**
* unregister this DragGestureRecognizer's Listeners with the Component
*
* subclasses must override this method
*/
protected void unregisterListeners() {
}
}
}