/*
* Copyright (C) 2012 Jason Gedge <http://www.gedge.ca>
*
* This file is part of the OpGraph project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*
*/
package ca.gedge.opgraph.app.commands.edit;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import ca.gedge.opgraph.OpGraph;
import ca.gedge.opgraph.OpNode;
import ca.gedge.opgraph.app.GraphDocument;
import ca.gedge.opgraph.io.OpGraphSerializer;
import ca.gedge.opgraph.io.OpGraphSerializerFactory;
import ca.gedge.opgraph.util.Pair;
/**
* Inner class for handling OpGraph clipboard content.
*/
class SubgraphClipboardContents implements Transferable {
/** Logger */
private static final Logger LOGGER = Logger.getLogger(SubgraphClipboardContents.class.getName());
/** Clipboard data flavor */
public static final DataFlavor copyFlavor = new DataFlavor(SubgraphClipboardContents.class, "SubgraphClipboardContents");
/** The document containing the graph to copy from */
public final GraphDocument document;
/** The copied sub-graph */
public final OpGraph subGraph;
/** Mapping from graph to the number of times it has been pasted into each graph */
public final Map<OpGraph, Integer> graphDuplicates = new HashMap<OpGraph, Integer>();
/** Cached data values */
private final Map<DataFlavor, Object> cachedData = new HashMap<DataFlavor, Object>();
/**
* Constructs a clipboard contents for a given document and the subgraph which is being copied.
*
* @param document the document containing the graph
* @param selectedGraph the subgraph which is being copied
*/
public SubgraphClipboardContents(GraphDocument document, OpGraph selectedGraph) {
this.document = document;
this.subGraph = selectedGraph;
this.graphDuplicates.put(document.getGraph(), new Integer(0));
}
/**
* Gets the bounding rectangle of a given set of nodes.
*
* @return the bounding rectangle of the given collection of nodes
*/
private Rectangle getBoundingRect(Collection<OpNode> nodes) {
int xmin = Integer.MAX_VALUE;
int xmax = Integer.MIN_VALUE;
int ymin = Integer.MAX_VALUE;
int ymax = Integer.MIN_VALUE;
for(OpNode node : nodes) {
final JComponent comp = node.getExtension(JComponent.class);
if(comp != null) {
final Dimension pref = comp.getPreferredSize();
xmin = Math.min(xmin, comp.getX());
xmax = Math.max(xmax, comp.getX() + pref.width);
ymin = Math.min(ymin, comp.getY());
ymax = Math.max(ymax, comp.getY() + pref.height);
}
}
return new Rectangle(xmin, ymin, xmax-xmin, ymax-ymin);
}
//
// Transferable overrides
//
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
// First, check to see if we've cached a value for this data flavor
Object retVal = cachedData.get(flavor);
if(retVal == null) {
if(flavor == copyFlavor) {
retVal = this;
} else if(flavor == DataFlavor.imageFlavor) {
// XXX create a temp graph of the selected nodes and links?
final Rectangle boundRect = getBoundingRect(subGraph.getVertices());
if(boundRect.width > 0 && boundRect.height > 0) {
final Dimension fullSize = new Dimension(boundRect.x + boundRect.width, boundRect.y + boundRect.height);
final BufferedImage img = new BufferedImage(boundRect.width, boundRect.height, BufferedImage.TYPE_INT_ARGB);
final Graphics2D g = (Graphics2D)img.getGraphics();
final Collection<OpNode> currentSelection = document.getSelectionModel().getSelectedNodes();
// Set clip and paint into temp buffer
final AffineTransform transform = AffineTransform.getTranslateInstance(-boundRect.getX(), -boundRect.getY());
g.setColor(new Color(255,255,255,0));
g.fill(new Rectangle(0, 0, fullSize.width, fullSize.height));
g.setTransform(transform);
g.setClip(boundRect);
// Paint to graphics (without selection)
document.getSelectionModel().setSelectedNodes(null);
document.getCanvas().paint(g);
document.getSelectionModel().setSelectedNodes(currentSelection);
// Create the image
//final ByteArrayOutputStream bout = new ByteArrayOutputStream();
//ImageIO.write(img, "png", bout);
//retVal = Toolkit.getDefaultToolkit().createImage(bout.toByteArray());
retVal = img;
}
} else if(flavor == DataFlavor.stringFlavor) {
// Store XML data for string flavor
final OpGraphSerializer serializer = OpGraphSerializerFactory.getDefaultSerializer();
if(serializer == null) {
// XXX Consider just spitting out the default java serializer byte data?
LOGGER.severe("No default serializer available");
return false;
}
// Write XML to byte stream
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
serializer.write(subGraph, bout);
final String graphString = bout.toString("UTF-8");
retVal = graphString;
} catch(IOException e) {
LOGGER.severe(e.getMessage());
retVal = "";
}
} else {
throw new UnsupportedFlavorException(flavor);
}
if(retVal != null)
cachedData.put(flavor, retVal);
}
return retVal;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{copyFlavor, DataFlavor.imageFlavor, DataFlavor.stringFlavor};
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return (flavor.equals(copyFlavor) || flavor.equals(DataFlavor.imageFlavor) || flavor.equals(DataFlavor.stringFlavor));
}
}