/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * 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: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.diagram.handler; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; import java.util.Set; import org.simantics.Simantics; import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.WriteGraph; import org.simantics.db.common.CommentMetadata; import org.simantics.db.common.request.IndexRoot; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.utils.OrderedSetUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.util.Layer0Utils; import org.simantics.diagram.flag.DiagramFlagPreferences; import org.simantics.diagram.flag.FlagLabelingScheme; import org.simantics.diagram.flag.FlagUtil; import org.simantics.diagram.flag.IOTableUtil; import org.simantics.diagram.flag.IOTablesInfo; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.diagram.synchronization.CopyAdvisor; import org.simantics.diagram.synchronization.IModifiableSynchronizationContext; import org.simantics.diagram.synchronization.SynchronizationHints; import org.simantics.diagram.synchronization.graph.AddElement; import org.simantics.diagram.synchronization.graph.CopyAdvisorUtil; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints; import org.simantics.diagram.synchronization.graph.MoveRouteGraphConnection; import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager; import org.simantics.diagram.ui.DiagramModelHints; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.diagram.DiagramHints; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.elementclass.FlagClass.Type; import org.simantics.g2d.elementclass.FlagHandler; import org.simantics.layer0.Layer0; import org.simantics.modeling.ModelingResources; import org.simantics.scenegraph.g2d.snap.ISnapAdvisor; import org.simantics.scl.commands.Command; import org.simantics.scl.commands.Commands; /** * @author Tuukka Lehtonen */ public final class CopyPasteUtil { static final EnumSet NODES = EnumSet.of(ElementType.Node); static final EnumSet CONNECTIONS = EnumSet.of(ElementType.Connection); public static final EnumSet CONNECTION_PARTS = EnumSet.of(ElementType.Edge, ElementType.BranchPoint); public static final EnumSet FLAGS = EnumSet.of(ElementType.Flag); static final EnumSet MONITORS = EnumSet.of(ElementType.Monitor); static final EnumSet OTHERS = EnumSet.of(ElementType.Other); static final EnumSet NODES_AND_EDGES = EnumSet.of(ElementType.Node); static final EnumSet NOT_FLAGS = EnumSet.complementOf(FLAGS); public static boolean isFlagsOnlySelection(IElementAssortment ea) { return !ea.containsAny(NOT_FLAGS) && ea.contains(FLAGS) && ea.count(ElementType.Flag) > 0; } public static boolean onlyFlagsWithoutCorrespondence(RequestProcessor processor, ElementObjectAssortment ea) throws DatabaseException { return isFlagsOnlySelection(ea) && checkFlagsCorrespondences(processor, ea.flags, false); } public static boolean onlyFlagsWithoutCorrespondence(ElementAssortment ea) { return isFlagsOnlySelection(ea) && checkFlagsCorrespondences(ea.flags, false); } /** * Check that all specified flag elements either have a correspondence or * don't. Flag collections with both connected and disconnected flags will * always return false; * * @param flags * @param expectedValue * @return * @throws DatabaseException */ public static boolean checkFlagsCorrespondences(RequestProcessor processor, final Iterable flags, final boolean expectedValue) throws DatabaseException { return processor.syncRequest(new UniqueRead() { @Override public Boolean perform(ReadGraph graph) throws DatabaseException { return checkFlagsCorrespondences(graph, flags, expectedValue); } }); } /** * Check that all specified flag elements either have a correspondence or * don't. Flag collections with both connected and disconnected flags will * always return false; * * @param flags * @param expectedValue * @return * @throws DatabaseException */ public static boolean checkFlagsCorrespondences(ReadGraph graph, Iterable flags, boolean expectedValue) throws DatabaseException { for (Resource flag : flags) { if (FlagUtil.isJoined(graph, flag) != expectedValue) { return false; } } return true; } /** * Check that all specified flag elements either have a correspondence or * don't. Flag collections with both connected and disconnected flags will * always return false; * * @param flags * @param expectedValue * @return */ public static boolean checkFlagsCorrespondences(Iterable flags, boolean expectedValue) { for (IElement flag : flags) { if (flagHasCorrespondence(flag) != expectedValue) { return false; } } return true; } /** * @param flags flags to test * @param expectedValue true to return true only * if all flags are external, false to return * true only if all flags are not external * @return * @throws DatabaseException */ public static boolean checkFlagExternality(RequestProcessor processor, final Iterable flags, final boolean expectedValue) throws DatabaseException { return processor.syncRequest(new UniqueRead() { @Override public Boolean perform(ReadGraph graph) throws DatabaseException { return checkFlagExternality(graph, flags, expectedValue); } }); } /** * @param flags flags to test * @param expectedValue true to return true only * if all flags are external, false to return * true only if all flags are not external * @return * @throws DatabaseException */ public static boolean checkFlagExternality(ReadGraph graph, Iterable flags, boolean expectedValue) throws DatabaseException { for (Resource flag : flags) if (FlagUtil.isExternal(graph, flag) != expectedValue) return false; return true; } /** * @param flags flags to test * @param expectedValue true to return true only * if all flags are external, false to return * true only if all flags are not external * @return */ public static boolean checkFlagExternality(Iterable flags, boolean expectedValue) { for (IElement flag : flags) if (flagIsExternal(flag) != expectedValue) return false; return true; } public static boolean flagHasCorrespondence(IElement flag) { FlagHandler fh = flag.getElementClass().getSingleItem(FlagHandler.class); if (fh == null) throw new IllegalArgumentException("Not a flag element: " + flag); //return (fh.getConnection(flag) != null || fh.getConnectionData(flag) != null); return fh.getConnectionData(flag) != null; } public static boolean flagIsExternal(IElement flag) { FlagHandler fh = flag.getElementClass().getSingleItem(FlagHandler.class); if (fh == null) throw new IllegalArgumentException("Not a flag element: " + flag); //return (fh.getConnection(flag) != null || fh.getConnectionData(flag) != null); return fh.isExternal(flag); } /** * @param graph * @param connection * @return * @throws DatabaseException */ public static Set gatherBranchPoints(ReadGraph graph, ElementObjectAssortment ea) throws DatabaseException { Set bps = new HashSet(); bps.addAll(ea.branchPoints); for (Resource connection : ea.connections) bps.addAll( getBranchPoints(graph, connection) ); return bps; } /** * @param connection * @return * @throws DatabaseException */ public static Set gatherRouteGraphConnections(ReadGraph graph, ElementObjectAssortment ea) throws DatabaseException { Set rgcs = new HashSet(); DiagramResource DIA = DiagramResource.getInstance(graph); for (Resource connection : ea.connections) { if (graph.isInstanceOf(connection, DIA.RouteGraphConnection)) rgcs.add(connection); } return rgcs; } /** * @param connection * @return * @throws DatabaseException */ public static Collection getBranchPoints(ReadGraph graph, Resource connection) throws DatabaseException { return graph.getObjects(connection, DiagramResource.getInstance(graph).HasBranchPoint); } /** * @param m * @param elements * @param xoffset * @param yoffset * @throws DatabaseException */ public static void moveElements(WriteGraph graph, Set elements, double xoffset, double yoffset) throws DatabaseException { for (Resource e : elements) { AffineTransform at = DiagramGraphUtil.getAffineTransform(graph, e); at.setTransform(at.getScaleX(), at.getShearY(), at.getShearX(), at.getScaleY(), at.getTranslateX() + xoffset, at.getTranslateY() + yoffset); DiagramGraphUtil.setTransform(graph, e, at); } } /** * @param m * @param elements * @param offset * @throws DatabaseException */ public static void moveElements(WriteGraph graph, Set elements, Point2D offset) throws DatabaseException { moveElements(graph, elements, offset.getX(), offset.getY()); } /** * @param m * @param elements * @param xoffset * @param yoffset * @throws DatabaseException */ public static void moveParentedElements(WriteGraph graph, PasteOperation op, Set elements, Resource parentRelation, double xoffset, double yoffset) throws DatabaseException { ModelingResources MOD = ModelingResources.getInstance(graph); for (Resource e : elements) { Resource referencedParentComponent = graph.getPossibleObject(e, parentRelation); if (referencedParentComponent == null) continue; Resource referencedElement = graph.getPossibleObject(referencedParentComponent, MOD.ComponentToElement); // Don't move the element if it's parent element is also included in the moved set of elements. if (referencedElement != null && op.ea.all.contains(referencedElement)) continue; AffineTransform at = DiagramGraphUtil.getAffineTransform(graph, e); at.setTransform(at.getScaleX(), at.getShearY(), at.getShearX(), at.getScaleY(), at.getTranslateX() + xoffset, at.getTranslateY() + yoffset); DiagramGraphUtil.setTransform(graph, e, at); } } /** * @param m * @param elements * @param xoffset * @param yoffset * @throws DatabaseException */ public static void moveMonitors(WriteGraph graph, PasteOperation op, Set elements, double xoffset, double yoffset) throws DatabaseException { moveParentedElements(graph, op, elements, DiagramResource.getInstance(graph).HasMonitorComponent, xoffset, yoffset); } /** * @param m * @param elements * @param xoffset * @param yoffset * @throws DatabaseException */ public static void moveReferenceElements(WriteGraph graph, PasteOperation op, Set elements, double xoffset, double yoffset) throws DatabaseException { moveParentedElements(graph, op, elements, ModelingResources.getInstance(graph).HasParentComponent, xoffset, yoffset); } /** * @param graph * @param connections * @param xoffset * @param yoffset * @throws DatabaseException */ public static void moveRouteGraphConnections(WriteGraph graph, Set connections, Point2D offset) throws DatabaseException { if(!connections.isEmpty()) { Command command = Commands.get(graph, "Simantics/Diagram/moveConnection"); Resource root = graph.syncRequest(new IndexRoot(connections.iterator().next())); for (Resource r : connections) command.execute(graph, root, r, offset.getX(), offset.getY()); } } public static void moveConnection(WriteGraph graph, Resource connection, double offsetX, double offsetY) throws DatabaseException { new MoveRouteGraphConnection(connection, offsetX, offsetY).perform(graph); } /** * @param ctx * @param source * @param target * @param offset */ public static void copyElementPosition(ICanvasContext ctx, IElement source, IElement target, Point2D offset) { Point2D pos = ElementUtils.getPos(source); double x = pos.getX() + offset.getX(); double y = pos.getY() + offset.getY(); ElementUtils.setPos(target, snap(ctx, new Point2D.Double(x, y))); } /** * @param ctx * @param source * @param target * @param offset * @throws DatabaseException */ public static AffineTransform copyElementPosition(WriteGraph graph, ICanvasContext ctx, Resource sourceElement, Resource targetElement, Point2D offset) throws DatabaseException { AffineTransform at = getCopyTransform(graph, ctx, sourceElement); Point2D snapped = snap(ctx, new Point2D.Double(at.getTranslateX() + offset.getX(), at.getTranslateY() + offset.getY())); at.setTransform(at.getScaleX(), at.getShearY(), at.getShearX(), at.getScaleY(), snapped.getX(), snapped.getY()); DiagramGraphUtil.setTransform(graph, targetElement, at); return at; } private static AffineTransform getCopyTransform(ReadGraph graph, ICanvasContext ctx, Resource sourceElement) throws DatabaseException { if(ctx != null){ Resource runtimeDiagram = (Resource)((IDiagram)ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM)).getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE); return DiagramGraphUtil.getDynamicAffineTransform(graph, runtimeDiagram, sourceElement); } else { return DiagramGraphUtil.getAffineTransform(graph, sourceElement); } } /** * @param ctx * @param p * @return */ public static Point2D snap(ICanvasContext ctx, Point2D p) { if (ctx != null) { ISnapAdvisor snapAdvisor = ctx.getHintStack().getHint(DiagramHints.SNAP_ADVISOR); if (snapAdvisor != null) snapAdvisor.snap(p); } return p; } // ------------------------------------------------------------------------ /** * Performs the operations related to a diagram-local cut-paste operation. * This default implementation will merely translate the selection specified * by the operation. * * @param op * @throws DatabaseException */ public static void localCutPaste(final PasteOperation op) throws DatabaseException { Simantics.getSession().sync(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { graph.markUndoPoint(); localCutPaste(graph, op); Layer0Utils.addCommentMetadata(graph, "Cutted " + op + " to local target"); } }); } /** * Performs the operations related to a diagram-local cut-paste operation. * This default implementation will merely translate the selection specified * by the operation. * * @param graph * @param op * @throws DatabaseException */ public static void localCutPaste(WriteGraph graph, PasteOperation op) throws DatabaseException { if (op.sameDiagram() && op.cut) { CopyPasteUtil.moveElements(graph, op.ea.nodes, op.offset); CopyPasteUtil.moveElements(graph, op.ea.flags, op.offset); if(!op.ea.flags.isEmpty()) { IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, op.targetDiagram); DiagramResource DIA = DiagramResource.getInstance(graph); for(Resource flag : op.ea.flags) { double[] transform = graph.getRelatedValue(flag, DIA.HasTransform, Bindings.DOUBLE_ARRAY); ioTablesInfo.updateBinding(graph, DIA, flag, transform[4], transform[5]); } } CopyPasteUtil.moveElements(graph, CopyPasteUtil.gatherBranchPoints(graph, op.ea), op.offset); CopyPasteUtil.moveRouteGraphConnections(graph, CopyPasteUtil.gatherRouteGraphConnections(graph, op.ea), op.offset); CopyPasteUtil.moveElements(graph, op.ea.others, op.offset); CopyPasteUtil.moveMonitors(graph, op, op.ea.monitors, op.offset.getX(), op.offset.getY()); CopyPasteUtil.moveReferenceElements(graph, op, op.ea.references, op.offset.getX(), op.offset.getY()); } } /** * @param op * @throws DatabaseException */ public static void continueFlags(final PasteOperation op) throws DatabaseException { Simantics.getSession().sync(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { continueFlags(graph, op); } }); } /** * @param graph * @param op * @throws DatabaseException */ public static void continueFlags(WriteGraph graph, final PasteOperation op) throws DatabaseException { IModifiableSynchronizationContext targetContext = (IModifiableSynchronizationContext) op.target.getHint(SynchronizationHints.CONTEXT); if (targetContext == null) throw new IllegalArgumentException("target diagram has no synchronization context"); CopyAdvisor ca = op.target.getHint(SynchronizationHints.COPY_ADVISOR); if (ca == null) throw new IllegalArgumentException("no copy advisor"); Layer0 L0 = Layer0.getInstance(graph); DiagramResource DIA = DiagramResource.getInstance(graph); FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph); int joinedFlags = 0; for (Resource src : op.ea.flags) { Resource sourceDiagram = graph.getPossibleObject(src, L0.PartOf); Resource copy = CopyAdvisorUtil.copy(targetContext, graph, ca, src, sourceDiagram, op.targetDiagram); if(copy == null) continue; OrderedSetUtils.add(graph, op.targetDiagram, copy); graph.claim(op.targetDiagram, L0.ConsistsOf, copy); AddElement.claimFreshElementName(graph, op.targetDiagram, copy); GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER); if (glm != null) { glm.removeFromAllLayers(graph, copy); glm.putElementOnVisibleLayers(op.target, graph, copy); } AffineTransform at = CopyPasteUtil.copyElementPosition(graph, op.ctx, src, copy, op.offset); Type type = FlagUtil.getFlagType(graph, src, Type.Out); FlagUtil.setFlagType(graph, copy, type.other()); FlagUtil.join(graph, src, copy); if (scheme != null) { String label = scheme.generateLabel(graph, op.targetDiagram); if (label != null) { graph.claimLiteral(src, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING); graph.claimLiteral(copy, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING); } } // Update flag table binding IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, op.targetDiagram); ioTablesInfo.updateBinding(graph, DIA, copy, at.getTranslateX(), at.getTranslateY()); ++joinedFlags; } if (joinedFlags > 0) { // Add comment to change set. CommentMetadata cm = graph.getMetadata(CommentMetadata.class); graph.addMetadata(cm.add("Continued " + joinedFlags + " flag(s)")); } } /** * @param op * @throws DatabaseException */ public static void performDefaultPaste(PasteOperation op) throws DatabaseException { Session session = Simantics.getSession(); new Paster(session, op).perform(); } }