]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/handler/CopyPasteUtil.java
Merge "Add missing javax.servlet-api bundle requirement for jersey bundles"
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / handler / CopyPasteUtil.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.handler;
13
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;
19 import java.util.Set;
20
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;
63
64 /**
65  * @author Tuukka Lehtonen
66  */
67 public final class CopyPasteUtil {
68
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);
77
78     public static boolean isFlagsOnlySelection(IElementAssortment ea) {
79         return !ea.containsAny(NOT_FLAGS)
80         && ea.contains(FLAGS)
81         && ea.count(ElementType.Flag) > 0;
82     }
83
84     public static boolean onlyFlagsWithoutCorrespondence(RequestProcessor processor, ElementObjectAssortment ea)
85             throws DatabaseException {
86         return isFlagsOnlySelection(ea)
87         && checkFlagsCorrespondences(processor, ea.flags, false);
88     }
89
90     public static boolean onlyFlagsWithoutCorrespondence(ElementAssortment ea) {
91         return isFlagsOnlySelection(ea)
92         && checkFlagsCorrespondences(ea.flags, false);
93     }
94
95     /**
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;
99      * 
100      * @param flags
101      * @param expectedValue
102      * @return
103      * @throws DatabaseException 
104      */
105     public static boolean checkFlagsCorrespondences(RequestProcessor processor, final Iterable<Resource> flags, final boolean expectedValue) throws DatabaseException {
106         return processor.syncRequest(new UniqueRead<Boolean>() {
107             @Override
108             public Boolean perform(ReadGraph graph) throws DatabaseException {
109                 return checkFlagsCorrespondences(graph, flags, expectedValue);
110             }
111         });
112     }
113
114     /**
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;
118      * 
119      * @param flags
120      * @param expectedValue
121      * @return
122      * @throws DatabaseException 
123      */
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) {
127                 return false;
128             }
129         }
130         return true;
131     }
132
133     /**
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;
137      * 
138      * @param flags
139      * @param expectedValue
140      * @return
141      */
142     public static boolean checkFlagsCorrespondences(Iterable<IElement> flags, boolean expectedValue) {
143         for (IElement flag : flags) {
144             if (flagHasCorrespondence(flag) != expectedValue) {
145                 return false;
146             }
147         }
148         return true;
149     }
150
151     /**
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
156      * @return
157      * @throws DatabaseException 
158      */
159     public static boolean checkFlagExternality(RequestProcessor processor, final Iterable<Resource> flags, final boolean expectedValue) throws DatabaseException {
160         return processor.syncRequest(new UniqueRead<Boolean>() {
161             @Override
162             public Boolean perform(ReadGraph graph) throws DatabaseException {
163                 return checkFlagExternality(graph, flags, expectedValue);
164             }
165         });
166     }
167
168     /**
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
173      * @return
174      * @throws DatabaseException 
175      */
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)
179                 return false;
180         return true;
181     }
182
183     /**
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
188      * @return
189      */
190     public static boolean checkFlagExternality(Iterable<IElement> flags, boolean expectedValue) {
191         for (IElement flag : flags)
192             if (flagIsExternal(flag) != expectedValue)
193                 return false;
194         return true;
195     }
196
197     public static boolean flagHasCorrespondence(IElement flag) {
198         FlagHandler fh = flag.getElementClass().getSingleItem(FlagHandler.class);
199         if (fh == null)
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;
203     }
204
205     public static boolean flagIsExternal(IElement flag) {
206         FlagHandler fh = flag.getElementClass().getSingleItem(FlagHandler.class);
207         if (fh == null)
208             throw new IllegalArgumentException("Not a flag element: " + flag);
209         //return (fh.getConnection(flag) != null || fh.getConnectionData(flag) != null);
210         return fh.isExternal(flag);
211     }
212
213     /**
214      * @param graph 
215      * @param connection
216      * @return
217      * @throws DatabaseException 
218      */
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) );
224         return bps;
225     }
226
227     /**
228      * @param connection
229      * @return
230      * @throws DatabaseException 
231      */
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);
238         }
239         return rgcs;
240     }
241
242     /**
243      * @param connection
244      * @return
245      * @throws DatabaseException 
246      */
247     public static Collection<Resource> getBranchPoints(ReadGraph graph, Resource connection) throws DatabaseException {
248         return graph.getObjects(connection, DiagramResource.getInstance(graph).HasBranchPoint);
249     }
250
251     /**
252      * @param m
253      * @param elements
254      * @param xoffset
255      * @param yoffset
256      * @throws DatabaseException 
257      */
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);
266         }
267     }
268
269     /**
270      * @param m
271      * @param elements
272      * @param offset
273      * @throws DatabaseException 
274      */
275     public static void moveElements(WriteGraph graph, Set<Resource> elements, Point2D offset) throws DatabaseException {
276         moveElements(graph, elements, offset.getX(), offset.getY());
277     }
278
279     /**
280      * @param m
281      * @param elements
282      * @param xoffset
283      * @param yoffset
284      * @throws DatabaseException 
285      */
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)
292                 continue;
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))
296                 continue;
297
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);
303         }
304     }
305
306     /**
307      * @param m
308      * @param elements
309      * @param xoffset
310      * @param yoffset
311      * @throws DatabaseException 
312      */
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);
316     }
317
318
319     /**
320      * @param m
321      * @param elements
322      * @param xoffset
323      * @param yoffset
324      * @throws DatabaseException 
325      */
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);
329     }
330
331     /**
332      * @param graph
333      * @param connections
334      * @param xoffset
335      * @param yoffset
336      * @throws DatabaseException 
337      */
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());
344         }
345     }
346     
347     public static void moveConnection(WriteGraph graph, Resource connection, double offsetX, double offsetY) throws DatabaseException {
348         new MoveRouteGraphConnection(connection, offsetX, offsetY).perform(graph);
349     }
350
351     /**
352      * @param ctx
353      * @param source
354      * @param target
355      * @param offset
356      */
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)));
362     }
363
364     /**
365      * @param ctx
366      * @param source
367      * @param target
368      * @param offset
369      * @throws DatabaseException 
370      */
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);
376         return at;
377     }
378     
379     private static AffineTransform getCopyTransform(ReadGraph graph, ICanvasContext ctx, Resource sourceElement) throws DatabaseException {
380         if(ctx != null){
381             Resource runtimeDiagram = (Resource)((IDiagram)ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM)).getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE);
382             return DiagramGraphUtil.getDynamicAffineTransform(graph, runtimeDiagram, sourceElement);
383         } else {
384             return DiagramGraphUtil.getAffineTransform(graph, sourceElement);
385         }
386     }
387
388     /**
389      * @param ctx
390      * @param p
391      * @return
392      */
393     public static Point2D snap(ICanvasContext ctx, Point2D p) {
394         if (ctx != null) {
395             ISnapAdvisor snapAdvisor = ctx.getHintStack().getHint(DiagramHints.SNAP_ADVISOR);
396             if (snapAdvisor != null)
397                 snapAdvisor.snap(p);
398         }
399         return p;
400     }
401
402     // ------------------------------------------------------------------------
403
404     /**
405      * Performs the operations related to a diagram-local cut-paste operation.
406      * This default implementation will merely translate the selection specified
407      * by the operation.
408      * 
409      * @param op
410      * @throws DatabaseException 
411      */
412     public static void localCutPaste(final PasteOperation op) throws DatabaseException {
413         Simantics.getSession().sync(new WriteRequest() {
414             @Override
415             public void perform(WriteGraph graph) throws DatabaseException {
416                 graph.markUndoPoint();
417                 localCutPaste(graph, op);
418                 Layer0Utils.addCommentMetadata(graph, "Cutted " + op + " to local target");
419             }
420         });
421     }
422
423     /**
424      * Performs the operations related to a diagram-local cut-paste operation.
425      * This default implementation will merely translate the selection specified
426      * by the operation.
427      * 
428      * @param graph
429      * @param op
430      * @throws DatabaseException 
431      */
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]);
442                 }
443             }
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());
449         }
450     }
451
452     /**
453      * @param op
454      * @throws DatabaseException 
455      */
456     public static void continueFlags(final PasteOperation op) throws DatabaseException {
457         Simantics.getSession().sync(new WriteRequest() {
458             @Override
459             public void perform(WriteGraph graph) throws DatabaseException {
460                 continueFlags(graph, op);
461             }
462         });
463     }
464
465     /**
466      * @param graph
467      * @param op
468      * @throws DatabaseException 
469      */
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");
474
475         CopyAdvisor ca = op.target.getHint(SynchronizationHints.COPY_ADVISOR);
476         if (ca == null)
477             throw new IllegalArgumentException("no copy advisor");
478
479         Layer0 L0 = Layer0.getInstance(graph);
480         DiagramResource DIA = DiagramResource.getInstance(graph);
481
482         FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);
483         int joinedFlags = 0;
484
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);
488             if(copy == null)
489                 continue;
490             OrderedSetUtils.add(graph, op.targetDiagram, copy);
491             graph.claim(op.targetDiagram, L0.ConsistsOf, copy);
492             AddElement.claimFreshElementName(graph, op.targetDiagram, copy);
493
494             GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);
495             if (glm != null) {
496                 glm.removeFromAllLayers(graph, copy);
497                 glm.putElementOnVisibleLayers(op.target, graph, copy);
498             }
499
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());
503
504             FlagUtil.join(graph, src, copy);
505
506             if (scheme != null) {
507                 String label = scheme.generateLabel(graph, op.targetDiagram);
508                 if (label != null) {
509                     graph.claimLiteral(src, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);
510                     graph.claimLiteral(copy, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);
511                 }
512             }
513
514             // Update flag table binding
515             IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, op.targetDiagram);
516             ioTablesInfo.updateBinding(graph, DIA, copy, at.getTranslateX(), at.getTranslateY());
517
518             ++joinedFlags;
519         }
520
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)"));
525         }
526     }
527
528     /**
529      * @param op
530      * @throws DatabaseException
531      */
532     public static void performDefaultPaste(PasteOperation op) throws DatabaseException {
533         Session session = Simantics.getSession();
534         new Paster(session, op).perform();
535     }
536
537 }