/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * DisplayedGemSelection.java * Creation date: October 21st 2002 * By: Ken Wong */ package org.openquark.gems.client; import java.awt.Point; import java.awt.Rectangle; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.openquark.cal.services.GemEntity; import org.openquark.cal.valuenode.ValueNode; import org.openquark.gems.client.CollectorGem.CollectingPart; import org.openquark.util.Pair; /** * This class is used to handle the copy/cut/paste actions that occur on the tabletop. It is a definition * of a transferable object for Displayed Gems. To make a copy of a selection, simply create a new instance of this * object, and then get the transferdata and create a new object. * * Creation Date: October 21st 2002 * @author Ken Wong * */ class DisplayedGemSelection implements ClipboardOwner, Transferable { /** * This is the basic dataflavor of this transferable. * It represents the Pair: (DisplayedGem[], List of Connection) */ public static final DataFlavor CONNECTION_PAIR_FLAVOR = new DataFlavor(Pair.class, "DisplayedGemConnectionPair"); /** Use this dataflavor to get the original location of the gems * Represents a Point object. */ public static final DataFlavor ORIGINAL_LOCATION_FLAVOR = new DataFlavor(Point.class, "DisplayedGemOriginalLocation"); /** the list of supported flavors */ private static final DataFlavor[] SUPPORTED_FLAVORS = {CONNECTION_PAIR_FLAVOR, ORIGINAL_LOCATION_FLAVOR}; /** a reference to the gemCutter */ private final GemCutter gemCutter; /** a copy of the originals which we use to make more copies */ private final DisplayedGem[] originalMolds; /** The origin of the bounds for the gems being copied. */ private final Point originalLocation; /** * A comparator that sorts code and collector gems relative to each other, * but doesn't apply any particular sort order to other gems. */ private static final Comparator<DisplayedGem> codeCollectorNameComparator = new Comparator<DisplayedGem>() { public int compare(DisplayedGem o1, DisplayedGem o2) { Gem gem1 = o1.getGem(); Gem gem2 = o2.getGem(); String name1; if (gem1 instanceof CodeGem) { name1 = ((CodeGem)gem1).getUnqualifiedName(); } else if (gem1 instanceof CollectorGem) { name1 = ((CollectorGem)gem1).getUnqualifiedName(); } else { name1 = ""; } String name2; if (gem2 instanceof CodeGem) { name2 = ((CodeGem)gem2).getUnqualifiedName(); } else if (gem2 instanceof CollectorGem) { name2 = ((CollectorGem)gem2).getUnqualifiedName(); } else { name2 = ""; } return name1.compareToIgnoreCase(name2); } }; /** * Default constructor for this transferable. * Actual creation of transfer objects does not occur until the 'get' call. * @param displayedGems the gems being copied/cut * @param gemCutter the Gem Cutter */ public DisplayedGemSelection(DisplayedGem[] displayedGems, GemCutter gemCutter) { this.gemCutter = gemCutter; Rectangle gemBounds = displayedGems[0].getBounds(); for (int i = 1; i < displayedGems.length; i++) { gemBounds.add(displayedGems[i].getBounds()); } Point currentOrigin = gemCutter.getTableTop().getOriginalOrigin(); originalLocation = new Point(gemBounds.x - currentOrigin.x, gemBounds.y - currentOrigin.y); // Build the 'mold', based on the top-left corner of the gem bounds. Pair<DisplayedGem[], List<Connection>> originals = getNewCopy(displayedGems, gemBounds.getLocation(), true); originalMolds = originals.fst(); List<Connection> connections = originals.snd(); // Sort so that the assignment of the numerical suffix to the collector and code gem names will // be in the same order as the originals on the tabletop. Collections.sort(Arrays.asList(originalMolds), codeCollectorNameComparator); // Bind the 'mold' connections. for (final Connection connection : connections) { Connection conn = connection; Gem.PartInput dest = conn.getDestination(); Gem.PartOutput source = conn.getSource(); dest.bindConnection(conn); source.bindConnection(conn); } } /** * Creates another copy of the originals. stored with the gems as the first item in the pair, and the * connections stored as the second object in the pair. * @param displayedGemOriginals the displayed gems to copy. * @param referenceOrigin the point to use as the origin of the copy operation. * Copied gem locations are adjusted so that the reference origin appears at (0,0). * @param asOriginals if true, makes a perfect clone (collector names are not changed to work on the TableTop). * if false, the resulting copy works on the TableTop. * @return Pair the copies of displayed gems and connections. */ private Pair<DisplayedGem[], List<Connection>> getNewCopy(DisplayedGem[] displayedGemOriginals, Point referenceOrigin, boolean asOriginals) { TableTop tableTop = gemCutter.getTableTop(); // Get the gems to copy. Set<Gem> gemsToCopySet = new HashSet<Gem>(); for (final DisplayedGem dGem : displayedGemOriginals) { gemsToCopySet.add(dGem.getGem()); } // Create a new list the same size as the old one DisplayedGem[] newDisplayedGems = new DisplayedGem[displayedGemOriginals.length]; // Create a list of the connections List<Connection> newConnections = new ArrayList<Connection>(); // A map from original gem to gem copy. Map<Gem, Gem> oldToNewGemMap = new HashMap<Gem, Gem>(); // To keep track of the names we've used up so far Set<String> collectorsCodeGemsNames = new HashSet<String>(tableTop.getGemGraph().getCodeGemsCollectorsNames()); List<Integer> emitterIndices = new ArrayList<Integer>(); // The original collector gems for which copies can be emitted by emitter copies // (rather than emitting the original collector). Set<Gem> originalCollectorsEmittableWhenCopied = new HashSet<Gem>(); Set<CollectorGem> copiedCollectors = new HashSet<CollectorGem>(); // So for every display gem selected... for (int i = 0; i < displayedGemOriginals.length; i++) { // Get the gem behind the displaygem Gem gem = displayedGemOriginals[i].getGem(); Point oldLocation = displayedGemOriginals[i].getLocation(); Point location = new Point(oldLocation.x - referenceOrigin.x, oldLocation.y - referenceOrigin.y); if (gem instanceof FunctionalAgentGem) { // get the SC behind this gem and produce a new one GemEntity gemEntity = ((FunctionalAgentGem)gem).getGemEntity(); newDisplayedGems[i] = tableTop.createDisplayedFunctionalAgentGem(location, gemEntity); } else if (gem instanceof CodeGem) { // Clone this codeGem and create a new DisplayedGem to associate with it CodeGem clone = ((CodeGem)gem).makeCopy(); // If not used as 'molds' then we want to find a new valid name. if (!asOriginals) { String oldName = clone.getUnqualifiedName(); String newName = Gem.getNextValidName(oldName, collectorsCodeGemsNames); clone.setName(newName); } newDisplayedGems[i] = tableTop.createDisplayedGem(clone, location); newDisplayedGems[i].updateDisplayedInputs(); collectorsCodeGemsNames.add(clone.getUnqualifiedName()); } else if (gem instanceof RecordFieldSelectionGem) { // Clone the RecordFieldSelectionGem and make a new DisplayedGem to associate with it RecordFieldSelectionGem clone = ((RecordFieldSelectionGem)gem).makeCopy(); newDisplayedGems[i] = tableTop.createDisplayedGem(clone, location); } else if (gem instanceof CollectorGem) { // Clone the collector and create a new display gem to associate with it. CollectorGem collectorGem = ((CollectorGem)gem).makeCopy(); // If not used as 'molds', then we want to find a new name if (!asOriginals) { // If a collector of this name already exists, then we want to search for the // next available name. Such that if "value2" is the name of the original, and // then we find its 'baseName' which is "value", then we count upwards beginning from // the numerical suffix of the original (which happens to be 2), and so we'll try // "value3" and "value4", until a name is available. String oldName = collectorGem.getUnqualifiedName(); String newName = Gem.getNextValidName(oldName, collectorsCodeGemsNames); collectorGem.setName(newName); } newDisplayedGems[i] = tableTop.createDisplayedGem(collectorGem, location); collectorsCodeGemsNames.add(collectorGem.getUnqualifiedName()); copiedCollectors.add((CollectorGem)gem); // Calculate whether simultaneously copied emitters should emit the original collector or the copy. if (shouldEmitCollectorCopy((CollectorGem)gem, gemsToCopySet)) { originalCollectorsEmittableWhenCopied.add(gem); } } else if (gem instanceof ReflectorGem) { // If it is an emitter, we don't deal with it until the end, because we want to know whether or not // it should emit its associated collector, or a copy of the collector. But we can't do this until // we loop through all of the collectors. So we'll store their indices and hold off until after the loop. emitterIndices.add(Integer.valueOf(i)); continue; } else if (gem instanceof ValueGem) { // Get The associated value node ValueNode valueNode = ((ValueGem)gem).getValueNode(); // Copy the valueNode but link it with a different typeExpr ValueNode valueNodeClone = valueNode.copyValueNode(); // Create the new valueGem ValueGem valueGemClone = new ValueGem(valueNodeClone); // Build the associated displayedGem newDisplayedGems[i] = tableTop.createDisplayedGem(valueGemClone, location); } else if (gem instanceof RecordCreationGem) { RecordCreationGem gemClone = ((RecordCreationGem)gem).makeCopy(); // Build the associated displayedGem newDisplayedGems[i] = tableTop.createDisplayedGem(gemClone, location); } else { throw new IllegalArgumentException("Unknown gem type: " + gem.getClass()); } oldToNewGemMap.put(gem, newDisplayedGems[i].getGem()); } // Go through the emitters that we ignored earlier Set<ReflectorGem> emittersToEmitCollectorCopies = new HashSet<ReflectorGem>(); // (Set of EmitterGem) emitters whose copies will emit collector copies. for (int i = 0, nEmitters = emitterIndices.size(); i < nEmitters; ++i) { int originalIndex = emitterIndices.get(i).intValue(); // Get the emitters ReflectorGem reflectorGem = (ReflectorGem)displayedGemOriginals[originalIndex].getGem(); Point location = displayedGemOriginals[originalIndex].getLocation(); // Shift their locations slightly location.x -= referenceOrigin.x; location.y -= referenceOrigin.y; CollectorGem reflectorCollector = reflectorGem.getCollector(); // Determine whether we should map the reflector to the original collector or the copy. // Note that the check ensures that the number of reflected inputs in the copy will be the same as in the original. if (originalCollectorsEmittableWhenCopied.contains(reflectorCollector)) { // Get the collector copy and create an emitter with the appropriate number of inputs. // Note that this emitter will be in an inconsistent state (until arguments state is set). CollectorGem collectorGem = (CollectorGem)oldToNewGemMap.get(reflectorCollector); ReflectorGem emitterGemCopy = new ReflectorGem(collectorGem, reflectorGem.getNInputs()); newDisplayedGems[originalIndex] = tableTop.createDisplayedGem(emitterGemCopy, location); emittersToEmitCollectorCopies.add(reflectorGem); } else { // Create an emitter for the original collector. newDisplayedGems[originalIndex] = tableTop.createDisplayedReflectorGem(location, reflectorCollector); } oldToNewGemMap.put(reflectorGem, newDisplayedGems[originalIndex].getGem()); } // Populate argument state for emitters emitting collector copies. for (final ReflectorGem originalEmitterGem : emittersToEmitCollectorCopies) { ReflectorGem emitterGemCopy = (ReflectorGem)oldToNewGemMap.get(originalEmitterGem); emitterGemCopy.copyArgumentState(originalEmitterGem, oldToNewGemMap); } // Set collector copy argument state for any copied collectors. for (final CollectorGem copiedCollector : copiedCollectors) { CollectorGem collectorCopy = (CollectorGem)oldToNewGemMap.get(copiedCollector); // Iterate over copied target arguments, and set their targets if possible. for (final Gem.PartInput targetingInput : copiedCollector.getTargetArguments()) { if (canTargetGemCopy(targetingInput, gemsToCopySet)) { Gem.PartInput inputCopy = oldToNewGemMap.get(targetingInput.getGem()).getInputPart(targetingInput.getInputNum()); collectorCopy.addArgument(inputCopy); } } } for (int i = 0; i < displayedGemOriginals.length; i++) { // Get the gem behind the displayed gem Gem oldGem = displayedGemOriginals[i].getGem(); Gem newGem = newDisplayedGems[i].getGem(); // loop through all the arguments of this gem so connections and metadatas and burns are recorded properly. for (int j = 0, nArgs = oldGem.getNInputs(); j < nArgs; j++) { Gem.PartInput newPartInput = newGem.getInputPart(j); Gem.PartInput oldPartInput = oldGem.getInputPart(j); // Copy the metadata and name. We don't do this for CollectingParts since they // don't have any metadata and automatically synchronize their name with the collector name. if (!(newPartInput instanceof CollectingPart)) { newPartInput.setArgumentName(oldPartInput.getArgumentName()); newPartInput.setDesignMetadata(oldPartInput.getDesignMetadata()); } // If it is burnt, then we want to have the same thing in the copy. if (oldPartInput.isBurnt()) { newPartInput.setBurnt(true); } } // If the output was connected, reconnect the copy if the input gem was also copied. Gem.PartOutput originalOutput = oldGem.getOutputPart(); if (originalOutput != null && originalOutput.isConnected()) { Gem.PartInput oldDestination = originalOutput.getConnection().getDestination(); Gem destinationGemCopy = oldToNewGemMap.get(oldDestination.getGem()); if (destinationGemCopy != null) { Gem.PartOutput newOutput = oldToNewGemMap.get(oldGem).getOutputPart(); Gem.PartInput newInput = destinationGemCopy.getInputPart(oldDestination.getInputNum()); Connection connection = new Connection(newOutput, newInput); newOutput.bindConnection(connection); newInput.bindConnection(connection); newConnections.add(connection); } } } // return the new displayed gems and connections return new Pair<DisplayedGem[], List<Connection>>(newDisplayedGems, newConnections); } /** * Return whether emitters for a given collector should emit the copy of the collector, or the original collector. * @param collectorGemToCopy the collector gem being copied. * @param gemsToCopy the gems being copied. * @return whether emitters for a given collector should emit the copy of the collector, or the original collector. * If true, the emitter should emit the definition of the copy of the collector. If false, it should emit the definition of the original. * False if the collector is not being copied. * True if any emitter inputs are connected, reflected inputs are all copied, and those inputs are all connected to the collector via * copied gems. */ private static boolean shouldEmitCollectorCopy(CollectorGem collectorGemToCopy, Set<Gem> gemsToCopy) { // Check that the collector gem is being copied. if (!gemsToCopy.contains(collectorGemToCopy)) { return false; } // If the collector doesn't reflect any inputs, we can return true right away.. List<Gem.PartInput> reflectedInputs = collectorGemToCopy.getReflectedInputs(); int nReflectedInputs = reflectedInputs.size(); if (nReflectedInputs == 0) { return true; } // Get any emitters for collectorGemToCopy being copied.. Set<ReflectorGem> emittersToCopy = new HashSet<ReflectorGem>(); // Iterate over the collector's emitters - in the worst case, this will be a lot better than iterating over gemsToCopy. for (final ReflectorGem emitterGem : collectorGemToCopy.getReflectors()) { if (gemsToCopy.contains(emitterGem)) { emittersToCopy.add(emitterGem); } } // Check for no emitters being copied.. if (emittersToCopy.isEmpty()) { return true; } // Uncomment this section to return true when all emitter inputs are unconnected. // This may result in the emitter changing its shape when copied. /* // Check whether any emitter inputs being copied are connected. boolean anyEmitterInputsConnected = false; for (Iterator it = emittersToCopy.iterator(); it.hasNext(); ) { ReflectorGem emitterGem = (ReflectorGem)it.next(); for (int i = 0; i < nReflectedInputs; i++) { if (emitterGem.getInputPart(i).isConnected()) { anyEmitterInputsConnected = true; break; } } } if (!anyEmitterInputsConnected) { return true; } /**/ // Return true if all reflected inputs are being copied, and connect to the collector being copied via copied gems // (ie. whether the reflected input copy can be retargeted to the collector's copy). // Check reflected inputs for connectivity with the collector being copied. for (final Gem.PartInput reflectedInput : reflectedInputs) { if (!canTargetGemCopy(reflectedInput, gemsToCopy)) { return false; } } // If we're here, all reflected inputs are connected to the target collector by gems which are also being copied. return true; } /** * Return whether a copy of the given input can target the target copy. * True if all gems between the input and the target are being copied. * @param inputToCopy the input being copied. * @param gemsToCopy the gems being copied. * @return whether a copy of the given input can target the target copy. */ private static final boolean canTargetGemCopy(Gem.PartInput inputToCopy, Set<Gem> gemsToCopy) { CollectorGem inputTarget = GemGraph.getInputArgumentTarget(inputToCopy); Gem gemToCheck = inputToCopy.getGem(); for (;;) { // If it's not being copied, return false. if (gemToCheck == null || !gemsToCopy.contains(gemToCheck)) { return false; } if (gemToCheck == inputTarget) { return true; } // Get the ancestor / target. gemToCheck = (gemToCheck instanceof CollectorGem) ? ((CollectorGem)gemToCheck).getTargetCollectorGem() : gemToCheck.getOutputPart().getConnectedGem(); } } /** * @see java.awt.datatransfer.ClipboardOwner#lostOwnership(Clipboard, Transferable) */ public void lostOwnership(Clipboard clipboard, Transferable contents) { } /** * @see java.awt.datatransfer.Transferable#getTransferDataFlavors() */ public DataFlavor[] getTransferDataFlavors() { return SUPPORTED_FLAVORS; } /** * @see java.awt.datatransfer.Transferable#isDataFlavorSupported(DataFlavor) */ public boolean isDataFlavorSupported(DataFlavor flavor) { return (flavor.equals(CONNECTION_PAIR_FLAVOR) || flavor.equals(ORIGINAL_LOCATION_FLAVOR)); } /** * @see java.awt.datatransfer.Transferable#getTransferData(DataFlavor) */ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { if (flavor.equals(CONNECTION_PAIR_FLAVOR)) { // Get a new copy of the information return getNewCopy(originalMolds, new Point(), false); } else if (flavor.equals(ORIGINAL_LOCATION_FLAVOR)){ Point currentOrigin = gemCutter.getTableTop().getOriginalOrigin(); return new Point(currentOrigin.x + originalLocation.x, currentOrigin.y + originalLocation.y); } else { // Not supported. throw new UnsupportedFlavorException(flavor); } } }