1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.diagram.handler;
14 import java.awt.geom.AffineTransform;
15 import java.awt.geom.Point2D;
16 import java.util.Collection;
17 import java.util.EnumSet;
18 import java.util.HashSet;
21 import org.simantics.Simantics;
22 import org.simantics.databoard.Bindings;
23 import org.simantics.db.ReadGraph;
24 import org.simantics.db.RequestProcessor;
25 import org.simantics.db.Resource;
26 import org.simantics.db.Session;
27 import org.simantics.db.WriteGraph;
28 import org.simantics.db.common.CommentMetadata;
29 import org.simantics.db.common.request.IndexRoot;
30 import org.simantics.db.common.request.UniqueRead;
31 import org.simantics.db.common.request.WriteRequest;
32 import org.simantics.db.common.utils.OrderedSetUtils;
33 import org.simantics.db.exception.DatabaseException;
34 import org.simantics.db.layer0.util.Layer0Utils;
35 import org.simantics.diagram.flag.DiagramFlagPreferences;
36 import org.simantics.diagram.flag.FlagLabelingScheme;
37 import org.simantics.diagram.flag.FlagUtil;
38 import org.simantics.diagram.flag.IOTableUtil;
39 import org.simantics.diagram.flag.IOTablesInfo;
40 import org.simantics.diagram.stubs.DiagramResource;
41 import org.simantics.diagram.synchronization.CopyAdvisor;
42 import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;
43 import org.simantics.diagram.synchronization.SynchronizationHints;
44 import org.simantics.diagram.synchronization.graph.AddElement;
45 import org.simantics.diagram.synchronization.graph.CopyAdvisorUtil;
46 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
47 import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;
48 import org.simantics.diagram.synchronization.graph.MoveRouteGraphConnection;
49 import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;
50 import org.simantics.diagram.ui.DiagramModelHints;
51 import org.simantics.g2d.canvas.ICanvasContext;
52 import org.simantics.g2d.diagram.DiagramHints;
53 import org.simantics.g2d.diagram.IDiagram;
54 import org.simantics.g2d.element.ElementUtils;
55 import org.simantics.g2d.element.IElement;
56 import org.simantics.g2d.elementclass.FlagClass.Type;
57 import org.simantics.g2d.elementclass.FlagHandler;
58 import org.simantics.layer0.Layer0;
59 import org.simantics.modeling.ModelingResources;
60 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
61 import org.simantics.scl.commands.Command;
62 import org.simantics.scl.commands.Commands;
65 * @author Tuukka Lehtonen
67 public final class CopyPasteUtil {
69 static final EnumSet<ElementType> NODES = EnumSet.of(ElementType.Node);
70 static final EnumSet<ElementType> CONNECTIONS = EnumSet.of(ElementType.Connection);
71 public static final EnumSet<ElementType> CONNECTION_PARTS = EnumSet.of(ElementType.Edge, ElementType.BranchPoint);
72 public static final EnumSet<ElementType> FLAGS = EnumSet.of(ElementType.Flag);
73 static final EnumSet<ElementType> MONITORS = EnumSet.of(ElementType.Monitor);
74 static final EnumSet<ElementType> OTHERS = EnumSet.of(ElementType.Other);
75 static final EnumSet<ElementType> NODES_AND_EDGES = EnumSet.of(ElementType.Node);
76 static final EnumSet<ElementType> NOT_FLAGS = EnumSet.complementOf(FLAGS);
78 public static boolean isFlagsOnlySelection(IElementAssortment ea) {
79 return !ea.containsAny(NOT_FLAGS)
81 && ea.count(ElementType.Flag) > 0;
84 public static boolean onlyFlagsWithoutCorrespondence(RequestProcessor processor, ElementObjectAssortment ea)
85 throws DatabaseException {
86 return isFlagsOnlySelection(ea)
87 && checkFlagsCorrespondences(processor, ea.flags, false);
90 public static boolean onlyFlagsWithoutCorrespondence(ElementAssortment ea) {
91 return isFlagsOnlySelection(ea)
92 && checkFlagsCorrespondences(ea.flags, false);
96 * Check that all specified flag elements either have a correspondence or
97 * don't. Flag collections with both connected and disconnected flags will
98 * always return false;
101 * @param expectedValue
103 * @throws DatabaseException
105 public static boolean checkFlagsCorrespondences(RequestProcessor processor, final Iterable<Resource> flags, final boolean expectedValue) throws DatabaseException {
106 return processor.syncRequest(new UniqueRead<Boolean>() {
108 public Boolean perform(ReadGraph graph) throws DatabaseException {
109 return checkFlagsCorrespondences(graph, flags, expectedValue);
115 * Check that all specified flag elements either have a correspondence or
116 * don't. Flag collections with both connected and disconnected flags will
117 * always return false;
120 * @param expectedValue
122 * @throws DatabaseException
124 public static boolean checkFlagsCorrespondences(ReadGraph graph, Iterable<Resource> flags, boolean expectedValue) throws DatabaseException {
125 for (Resource flag : flags) {
126 if (FlagUtil.isJoined(graph, flag) != expectedValue) {
134 * Check that all specified flag elements either have a correspondence or
135 * don't. Flag collections with both connected and disconnected flags will
136 * always return false;
139 * @param expectedValue
142 public static boolean checkFlagsCorrespondences(Iterable<IElement> flags, boolean expectedValue) {
143 for (IElement flag : flags) {
144 if (flagHasCorrespondence(flag) != expectedValue) {
152 * @param flags flags to test
153 * @param expectedValue <code>true</code> to return <code>true</code> only
154 * if all flags are external, <code>false</code> to return
155 * <code>true</code> only if all flags are not external
157 * @throws DatabaseException
159 public static boolean checkFlagExternality(RequestProcessor processor, final Iterable<Resource> flags, final boolean expectedValue) throws DatabaseException {
160 return processor.syncRequest(new UniqueRead<Boolean>() {
162 public Boolean perform(ReadGraph graph) throws DatabaseException {
163 return checkFlagExternality(graph, flags, expectedValue);
169 * @param flags flags to test
170 * @param expectedValue <code>true</code> to return <code>true</code> only
171 * if all flags are external, <code>false</code> to return
172 * <code>true</code> only if all flags are not external
174 * @throws DatabaseException
176 public static boolean checkFlagExternality(ReadGraph graph, Iterable<Resource> flags, boolean expectedValue) throws DatabaseException {
177 for (Resource flag : flags)
178 if (FlagUtil.isExternal(graph, flag) != expectedValue)
184 * @param flags flags to test
185 * @param expectedValue <code>true</code> to return <code>true</code> only
186 * if all flags are external, <code>false</code> to return
187 * <code>true</code> only if all flags are not external
190 public static boolean checkFlagExternality(Iterable<IElement> flags, boolean expectedValue) {
191 for (IElement flag : flags)
192 if (flagIsExternal(flag) != expectedValue)
197 public static boolean flagHasCorrespondence(IElement flag) {
198 FlagHandler fh = flag.getElementClass().getSingleItem(FlagHandler.class);
200 throw new IllegalArgumentException("Not a flag element: " + flag);
201 //return (fh.getConnection(flag) != null || fh.getConnectionData(flag) != null);
202 return fh.getConnectionData(flag) != null;
205 public static boolean flagIsExternal(IElement flag) {
206 FlagHandler fh = flag.getElementClass().getSingleItem(FlagHandler.class);
208 throw new IllegalArgumentException("Not a flag element: " + flag);
209 //return (fh.getConnection(flag) != null || fh.getConnectionData(flag) != null);
210 return fh.isExternal(flag);
217 * @throws DatabaseException
219 public static Set<Resource> gatherBranchPoints(ReadGraph graph, ElementObjectAssortment ea) throws DatabaseException {
220 Set<Resource> bps = new HashSet<Resource>();
221 bps.addAll(ea.branchPoints);
222 for (Resource connection : ea.connections)
223 bps.addAll( getBranchPoints(graph, connection) );
230 * @throws DatabaseException
232 public static Set<Resource> gatherRouteGraphConnections(ReadGraph graph, ElementObjectAssortment ea) throws DatabaseException {
233 Set<Resource> rgcs = new HashSet<Resource>();
234 DiagramResource DIA = DiagramResource.getInstance(graph);
235 for (Resource connection : ea.connections) {
236 if (graph.isInstanceOf(connection, DIA.RouteGraphConnection))
237 rgcs.add(connection);
245 * @throws DatabaseException
247 public static Collection<Resource> getBranchPoints(ReadGraph graph, Resource connection) throws DatabaseException {
248 return graph.getObjects(connection, DiagramResource.getInstance(graph).HasBranchPoint);
256 * @throws DatabaseException
258 public static void moveElements(WriteGraph graph, Set<Resource> elements, double xoffset, double yoffset)
259 throws DatabaseException {
260 for (Resource e : elements) {
261 AffineTransform at = DiagramGraphUtil.getAffineTransform(graph, e);
262 at.setTransform(at.getScaleX(), at.getShearY(), at.getShearX(), at.getScaleY(),
263 at.getTranslateX() + xoffset,
264 at.getTranslateY() + yoffset);
265 DiagramGraphUtil.setTransform(graph, e, at);
273 * @throws DatabaseException
275 public static void moveElements(WriteGraph graph, Set<Resource> elements, Point2D offset) throws DatabaseException {
276 moveElements(graph, elements, offset.getX(), offset.getY());
284 * @throws DatabaseException
286 public static void moveParentedElements(WriteGraph graph, PasteOperation op, Set<Resource> elements, Resource parentRelation, double xoffset, double yoffset)
287 throws DatabaseException {
288 ModelingResources MOD = ModelingResources.getInstance(graph);
289 for (Resource e : elements) {
290 Resource referencedParentComponent = graph.getPossibleObject(e, parentRelation);
291 if (referencedParentComponent == null)
293 Resource referencedElement = graph.getPossibleObject(referencedParentComponent, MOD.ComponentToElement);
294 // Don't move the element if it's parent element is also included in the moved set of elements.
295 if (referencedElement != null && op.ea.all.contains(referencedElement))
298 AffineTransform at = DiagramGraphUtil.getAffineTransform(graph, e);
299 at.setTransform(at.getScaleX(), at.getShearY(), at.getShearX(), at.getScaleY(),
300 at.getTranslateX() + xoffset,
301 at.getTranslateY() + yoffset);
302 DiagramGraphUtil.setTransform(graph, e, at);
311 * @throws DatabaseException
313 public static void moveMonitors(WriteGraph graph, PasteOperation op, Set<Resource> elements, double xoffset, double yoffset)
314 throws DatabaseException {
315 moveParentedElements(graph, op, elements, DiagramResource.getInstance(graph).HasMonitorComponent, xoffset, yoffset);
324 * @throws DatabaseException
326 public static void moveReferenceElements(WriteGraph graph, PasteOperation op, Set<Resource> elements, double xoffset, double yoffset)
327 throws DatabaseException {
328 moveParentedElements(graph, op, elements, ModelingResources.getInstance(graph).HasParentComponent, xoffset, yoffset);
336 * @throws DatabaseException
338 public static void moveRouteGraphConnections(WriteGraph graph, Set<Resource> connections, Point2D offset) throws DatabaseException {
339 if(!connections.isEmpty()) {
340 Command command = Commands.get(graph, "Simantics/Diagram/moveConnection");
341 Resource root = graph.syncRequest(new IndexRoot(connections.iterator().next()));
342 for (Resource r : connections)
343 command.execute(graph, root, r, offset.getX(), offset.getY());
347 public static void moveConnection(WriteGraph graph, Resource connection, double offsetX, double offsetY) throws DatabaseException {
348 new MoveRouteGraphConnection(connection, offsetX, offsetY).perform(graph);
357 public static void copyElementPosition(ICanvasContext ctx, IElement source, IElement target, Point2D offset) {
358 Point2D pos = ElementUtils.getPos(source);
359 double x = pos.getX() + offset.getX();
360 double y = pos.getY() + offset.getY();
361 ElementUtils.setPos(target, snap(ctx, new Point2D.Double(x, y)));
369 * @throws DatabaseException
371 public static AffineTransform copyElementPosition(WriteGraph graph, ICanvasContext ctx, Resource sourceElement, Resource targetElement, Point2D offset) throws DatabaseException {
372 AffineTransform at = getCopyTransform(graph, ctx, sourceElement);
373 Point2D snapped = snap(ctx, new Point2D.Double(at.getTranslateX() + offset.getX(), at.getTranslateY() + offset.getY()));
374 at.setTransform(at.getScaleX(), at.getShearY(), at.getShearX(), at.getScaleY(), snapped.getX(), snapped.getY());
375 DiagramGraphUtil.setTransform(graph, targetElement, at);
379 private static AffineTransform getCopyTransform(ReadGraph graph, ICanvasContext ctx, Resource sourceElement) throws DatabaseException {
381 Resource runtimeDiagram = (Resource)((IDiagram)ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM)).getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE);
382 return DiagramGraphUtil.getDynamicAffineTransform(graph, runtimeDiagram, sourceElement);
384 return DiagramGraphUtil.getAffineTransform(graph, sourceElement);
393 public static Point2D snap(ICanvasContext ctx, Point2D p) {
395 ISnapAdvisor snapAdvisor = ctx.getHintStack().getHint(DiagramHints.SNAP_ADVISOR);
396 if (snapAdvisor != null)
402 // ------------------------------------------------------------------------
405 * Performs the operations related to a diagram-local cut-paste operation.
406 * This default implementation will merely translate the selection specified
410 * @throws DatabaseException
412 public static void localCutPaste(final PasteOperation op) throws DatabaseException {
413 Simantics.getSession().sync(new WriteRequest() {
415 public void perform(WriteGraph graph) throws DatabaseException {
416 graph.markUndoPoint();
417 localCutPaste(graph, op);
418 Layer0Utils.addCommentMetadata(graph, "Cutted " + op + " to local target");
424 * Performs the operations related to a diagram-local cut-paste operation.
425 * This default implementation will merely translate the selection specified
430 * @throws DatabaseException
432 public static void localCutPaste(WriteGraph graph, PasteOperation op) throws DatabaseException {
433 if (op.sameDiagram() && op.cut) {
434 CopyPasteUtil.moveElements(graph, op.ea.nodes, op.offset);
435 CopyPasteUtil.moveElements(graph, op.ea.flags, op.offset);
436 if(!op.ea.flags.isEmpty()) {
437 IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, op.targetDiagram);
438 DiagramResource DIA = DiagramResource.getInstance(graph);
439 for(Resource flag : op.ea.flags) {
440 double[] transform = graph.getRelatedValue(flag, DIA.HasTransform, Bindings.DOUBLE_ARRAY);
441 ioTablesInfo.updateBinding(graph, DIA, flag, transform[4], transform[5]);
444 CopyPasteUtil.moveElements(graph, CopyPasteUtil.gatherBranchPoints(graph, op.ea), op.offset);
445 CopyPasteUtil.moveRouteGraphConnections(graph, CopyPasteUtil.gatherRouteGraphConnections(graph, op.ea), op.offset);
446 CopyPasteUtil.moveElements(graph, op.ea.others, op.offset);
447 CopyPasteUtil.moveMonitors(graph, op, op.ea.monitors, op.offset.getX(), op.offset.getY());
448 CopyPasteUtil.moveReferenceElements(graph, op, op.ea.references, op.offset.getX(), op.offset.getY());
454 * @throws DatabaseException
456 public static void continueFlags(final PasteOperation op) throws DatabaseException {
457 Simantics.getSession().sync(new WriteRequest() {
459 public void perform(WriteGraph graph) throws DatabaseException {
460 continueFlags(graph, op);
468 * @throws DatabaseException
470 public static void continueFlags(WriteGraph graph, final PasteOperation op) throws DatabaseException {
471 IModifiableSynchronizationContext targetContext = (IModifiableSynchronizationContext) op.target.getHint(SynchronizationHints.CONTEXT);
472 if (targetContext == null)
473 throw new IllegalArgumentException("target diagram has no synchronization context");
475 CopyAdvisor ca = op.target.getHint(SynchronizationHints.COPY_ADVISOR);
477 throw new IllegalArgumentException("no copy advisor");
479 Layer0 L0 = Layer0.getInstance(graph);
480 DiagramResource DIA = DiagramResource.getInstance(graph);
482 FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);
485 for (Resource src : op.ea.flags) {
486 Resource sourceDiagram = graph.getPossibleObject(src, L0.PartOf);
487 Resource copy = CopyAdvisorUtil.copy(targetContext, graph, ca, src, sourceDiagram, op.targetDiagram);
490 OrderedSetUtils.add(graph, op.targetDiagram, copy);
491 graph.claim(op.targetDiagram, L0.ConsistsOf, copy);
492 AddElement.claimFreshElementName(graph, op.targetDiagram, copy);
494 GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);
496 glm.removeFromAllLayers(graph, copy);
497 glm.putElementOnVisibleLayers(op.target, graph, copy);
500 AffineTransform at = CopyPasteUtil.copyElementPosition(graph, op.ctx, src, copy, op.offset);
501 Type type = FlagUtil.getFlagType(graph, src, Type.Out);
502 FlagUtil.setFlagType(graph, copy, type.other());
504 FlagUtil.join(graph, src, copy);
506 if (scheme != null) {
507 String label = scheme.generateLabel(graph, op.targetDiagram);
509 graph.claimLiteral(src, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);
510 graph.claimLiteral(copy, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);
514 // Update flag table binding
515 IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, op.targetDiagram);
516 ioTablesInfo.updateBinding(graph, DIA, copy, at.getTranslateX(), at.getTranslateY());
521 if (joinedFlags > 0) {
522 // Add comment to change set.
523 CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
524 graph.addMetadata(cm.add("Continued " + joinedFlags + " flag(s)"));
530 * @throws DatabaseException
532 public static void performDefaultPaste(PasteOperation op) throws DatabaseException {
533 Session session = Simantics.getSession();
534 new Paster(session, op).perform();