12d7700353b4fc7c3985456e13fa825b9523dfb2
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / block / actions / RegionToSuperblockAction.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - DIGITEO - Clement DAVID
4  * Copyright (C) 2011-2015 - Scilab Enterprises - Clement DAVID
5  *
6  * Copyright (C) 2012 - 2016 - Scilab Enterprises
7  *
8  * This file is hereby licensed under the terms of the GNU GPL v2.0,
9  * pursuant to article 5.3.4 of the CeCILL v.2.1.
10  * This file was originally licensed under the terms of the CeCILL v2.1,
11  * and continues to be available under such terms.
12  * For more information, see the COPYING file which you should have received
13  * along with this program.
14  *
15  */
16
17 package org.scilab.modules.xcos.block.actions;
18
19 import java.awt.event.ActionEvent;
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.LinkedList;
23 import java.util.Queue;
24 import java.util.TreeSet;
25 import java.util.logging.Logger;
26
27 import org.scilab.modules.graph.ScilabComponent;
28 import org.scilab.modules.graph.ScilabGraph;
29 import org.scilab.modules.graph.actions.base.VertexSelectionDependantAction;
30 import org.scilab.modules.gui.menuitem.MenuItem;
31 import org.scilab.modules.xcos.block.BasicBlock;
32 import org.scilab.modules.xcos.block.SuperBlock;
33 import org.scilab.modules.xcos.block.io.ContextUpdate;
34 import org.scilab.modules.xcos.block.io.ContextUpdate.IOBlocks;
35 import org.scilab.modules.xcos.graph.XcosDiagram;
36 import org.scilab.modules.xcos.link.BasicLink;
37 import org.scilab.modules.xcos.port.BasicPort;
38 import org.scilab.modules.xcos.port.control.ControlPort;
39 import org.scilab.modules.xcos.port.input.InputPort;
40 import org.scilab.modules.xcos.port.output.OutputPort;
41 import org.scilab.modules.xcos.utils.BlockPositioning;
42 import org.scilab.modules.xcos.utils.XcosMessages;
43
44 import com.mxgraph.model.mxGeometry;
45 import com.mxgraph.model.mxGraphModel;
46 import com.mxgraph.model.mxICell;
47 import com.mxgraph.util.mxPoint;
48 import com.mxgraph.util.mxRectangle;
49 import java.nio.LongBuffer;
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.logging.Level;
53 import static java.util.stream.Collectors.toList;
54 import org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement;
55 import org.scilab.modules.graph.utils.StyleMap;
56 import org.scilab.modules.types.ScilabString;
57 import org.scilab.modules.xcos.JavaController;
58 import org.scilab.modules.xcos.Kind;
59 import org.scilab.modules.xcos.ObjectProperties;
60 import org.scilab.modules.xcos.VectorOfDouble;
61 import org.scilab.modules.xcos.VectorOfInt;
62 import org.scilab.modules.xcos.VectorOfScicosID;
63 import org.scilab.modules.xcos.graph.model.XcosCell;
64 import org.scilab.modules.xcos.graph.model.XcosCellFactory;
65 import org.scilab.modules.xcos.io.ScilabTypeCoder;
66 import org.scilab.modules.xcos.utils.XcosConstants;
67
68 /**
69  * Move the Selected cells to a new SuperBlock diagram
70  */
71 // CSOFF: ClassFanOutComplexity
72 @SuppressWarnings(value = { "serial" })
73 public class RegionToSuperblockAction extends VertexSelectionDependantAction {
74     /** Name of the action */
75     public static final String NAME = XcosMessages.REGION_TO_SUPERBLOCK;
76     /** Icon name of the action */
77     public static final String SMALL_ICON = "object-group";
78     /** Mnemonic key of the action */
79     public static final int MNEMONIC_KEY = 0;
80     /** Accelerator key for the action */
81     public static final int ACCELERATOR_KEY = 0;
82
83     private static final String INTERFUNCTION_NAME = "SUPER_f";
84
85     /**
86      * Default Constructor
87      *
88      * @param scilabGraph
89      *            the graph
90      */
91     public RegionToSuperblockAction(ScilabGraph scilabGraph) {
92         super(scilabGraph);
93     }
94
95     /**
96      * @param scilabGraph
97      *            graph
98      * @return menu item
99      */
100     public static MenuItem createMenu(ScilabGraph scilabGraph) {
101         return createMenu(scilabGraph, RegionToSuperblockAction.class);
102     }
103
104     /**
105      * Represent a broken link at selection time
106      */
107     private static final class Broken implements Comparable<Broken> {
108         private final mxGraphModel parentModel;
109
110         private final BasicPort source;
111         private final BasicPort target;
112
113         private final BasicLink parentLink;
114         private final boolean containsSource;
115
116         /*
117          * Out of selection port position
118          */
119         private final double x;
120         private final double y;
121
122         /*
123          * lazy allocated fields
124          */
125         private BasicLink childLink;
126         private BasicPort parentPort;
127         private ContextUpdate childBlock;
128
129         /**
130          * Default Constructor
131          *
132          * @param parentModel
133          *            the parent model
134          * @param source
135          *            the source
136          * @param target
137          *            the target
138          * @param link
139          *            the link
140          * @param containsSource
141          *            is the source in selection
142          */
143         public Broken(mxGraphModel parentModel, BasicPort source, BasicPort target, BasicLink link, boolean containsSource) {
144             super();
145             this.parentModel = parentModel;
146
147             this.source = source;
148             this.target = target;
149             this.parentLink = link;
150             this.containsSource = containsSource;
151
152             final BasicPort terminal;
153             if (containsSource) {
154                 terminal = target;
155             } else {
156                 terminal = source;
157             }
158
159             /*
160              * Update position
161              */
162             final mxGeometry pos = parentModel.getGeometry(terminal);
163             final mxGeometry parent = parentModel.getGeometry(parentModel.getParent(terminal));
164             if (pos != null && parent != null) {
165                 this.x = pos.getX() + parent.getX() + (pos.getWidth() / 2) - (parent.getWidth() / 2);
166                 this.y = pos.getY() + parent.getY() + (pos.getHeight() / 2) - (parent.getHeight() / 2);
167             } else {
168                 this.x = 0.0;
169                 this.y = 0.0;
170             }
171         }
172
173         /**
174          * @return the link on the parent diagram
175          */
176         public BasicLink getParentLink() {
177             return parentLink;
178         }
179
180         /**
181          * @param isSource
182          *            is a source port needed
183          * @return the parent terminal (source or not)
184          */
185         public mxICell getParentTerminal(boolean isSource) {
186             final mxICell ret;
187
188             if (containsSource) {
189                 if (isSource) {
190                     ret = getParentPort();
191                 } else {
192                     ret = target;
193                 }
194             } else {
195                 if (isSource) {
196                     ret = source;
197                 } else {
198                     ret = getParentPort();
199                 }
200             }
201
202             return ret;
203         }
204
205         /**
206          * @param isSource
207          *            is a source port needed
208          * @return the child terminal (source or not)
209          */
210         public mxICell getChildTerminal(boolean isSource) {
211             final mxICell ret;
212
213             if (containsSource) {
214                 if (isSource) {
215                     ret = source;
216                 } else {
217                     ret = getChildBlock().getChildAt(0);
218                 }
219             } else {
220                 if (isSource) {
221                     ret = getChildBlock().getChildAt(0);
222                 } else {
223                     ret = target;
224                 }
225             }
226
227             return ret;
228         }
229
230         /**
231          * @return the terminal in the selection
232          */
233         public BasicPort getTerminal() {
234             if (containsSource) {
235                 return source;
236             } else {
237                 return target;
238             }
239         }
240
241         /**
242          * @return the link on the child diagram
243          */
244         public BasicLink getChildLink() {
245             if (childLink == null) {
246                 childLink = (BasicLink) parentModel.cloneCells(new Object[] { parentLink }, true)[0];
247             }
248             return childLink;
249         }
250
251         /**
252          * @return the port on the parent diagram
253          */
254         public BasicPort getParentPort() {
255             if (parentPort == null) {
256                 JavaController controller = new JavaController();
257                 long uid = controller.createObject(Kind.PORT);
258
259                 try {
260                     if (containsSource) {
261                         parentPort = IOBlocks.getOpposite(target.getClass()).getConstructor(Long.TYPE).newInstance(uid);
262                     } else {
263                         parentPort = IOBlocks.getOpposite(source.getClass()).getConstructor(Long.TYPE).newInstance(uid);
264                     }
265                 } catch (ReflectiveOperationException e) {
266                     Logger.getLogger(RegionToSuperblockAction.class.getName()).severe(e.toString());
267                 } catch (IllegalArgumentException ex) {
268                     Logger.getLogger(RegionToSuperblockAction.class.getName()).log(Level.SEVERE, null, ex);
269                 }
270             }
271
272             return parentPort;
273         }
274
275         /**
276          * @return the child block to put on the child diagram
277          */
278         public ContextUpdate getChildBlock() {
279             if (childBlock == null) {
280                 final BasicPort terminal = getTerminal();
281                 childBlock = IOBlocks.createBlock(terminal);
282
283                 /*
284                  * Set the child position
285                  */
286                 childBlock.getGeometry().setX(x);
287                 childBlock.getGeometry().setY(y);
288             }
289
290             return childBlock;
291         }
292
293         /**
294          * Set the ordering on the I/O block and port
295
296          * @param ordering
297          *            the ordering to set
298          */
299         public void setOrdering(JavaController controller, int ordering) {
300             // update the child ordering
301             VectorOfInt ipar = new VectorOfInt(1);
302             ipar.set(0, ordering);
303             controller.setObjectProperty(getChildBlock().getUID(), getChildBlock().getKind(), ObjectProperties.IPAR, ipar);
304
305             VectorOfDouble exprs = new ScilabTypeCoder().var2vec(new ScilabString(Integer.toString(ordering)));
306             controller.setObjectProperty(getChildBlock().getUID(), getChildBlock().getKind(), ObjectProperties.EXPRS, exprs);
307         }
308
309         /**
310          * {@inheritDoc}
311          *
312          * This function is used to sort a {@link TreeSet} of {@link Broken}.
313          */
314         @Override
315         public int compareTo(Broken o) {
316             final int xdiff = (int) (x - o.x);
317             final int ydiff = (int) (y - o.y);
318
319             if (xdiff == 0 && ydiff == 0) {
320                 // same position, sort by hashcode
321                 return hashCode() - o.hashCode();
322             } else {
323                 return (ydiff << (Integer.SIZE / 2)) + xdiff;
324             }
325         }
326
327         /**
328          * {@inheritDoc}
329          */
330         @Override
331         public String toString() {
332             final String labelSep = ": ";
333             final String link = " -> ";
334
335             final StringBuilder str = new StringBuilder();
336             if (getParentLink().getChildCount() > 0) {
337                 // append the label
338                 str.append(getParentLink().getChildAt(0).getValue());
339                 str.append(labelSep);
340             }
341
342             str.append(getParentTerminal(true));
343             str.append(link);
344             str.append(getParentTerminal(false));
345
346             str.append('\n');
347
348             if (getChildLink().getChildCount() > 0) {
349                 // append the label
350                 str.append(getChildLink().getChildAt(0).getValue());
351                 str.append(labelSep);
352             }
353
354             str.append(getChildTerminal(true));
355             str.append(link);
356             str.append(getChildTerminal(false));
357
358             return str.toString();
359         }
360     }
361
362     /**
363      * {@inheritDoc}
364      */
365     @Override
366     public void actionPerformed(ActionEvent e) {
367         final XcosDiagram parentGraph = (XcosDiagram) getGraph(e);
368
369         // action disabled when the cell is edited
370         final ScilabComponent comp = ((ScilabComponent) parentGraph.getAsComponent());
371         if (comp.isEditing()) {
372             return;
373         }
374
375         parentGraph.info(XcosMessages.GENERATE_SUPERBLOCK);
376         parentGraph.getModel().beginUpdate();
377         try {
378             final JavaController controller = new JavaController();
379             final SuperBlock superBlock;
380             final Collection<Broken> brokenLinks;
381
382             /*
383              * Allocate superBlock
384              */
385             final Object[] selection = parentGraph.getSelectionCells();
386             List<XcosCell> toBeMoved = Arrays.stream(selection)
387                                        .filter(o -> o instanceof XcosCell)
388                                        .map(o -> (XcosCell) o)
389                                        .collect(toList());
390
391             superBlock = allocateSuperBlock(controller, parentGraph, selection);
392
393             /*
394              * First perform all modification on the parent diagram to handle
395              * well undo/redo operations.
396              */
397             brokenLinks = updateParent(controller, parentGraph, superBlock, toBeMoved);
398
399             /*
400              * Then move some cells to the child diagram
401              */
402             moveToChild(controller, parentGraph, superBlock, brokenLinks, toBeMoved);
403
404             BlockPositioning.updateBlockView(parentGraph, superBlock);
405         } catch (ScilabInterpreterManagement.InterpreterException ex) {
406             // Scilab seems to be blocked, just consume the exception at this point
407         } finally {
408             parentGraph.getModel().endUpdate();
409             parentGraph.info(XcosMessages.EMPTY_INFO);
410         }
411     }
412
413     /**
414      * Allocate a superBlock
415      *
416      * @param parentGraph
417      *            the base graph
418      * @param selection
419      *            the selected blocks
420      * @return the allocated super block (without specific listeners)
421      */
422     private SuperBlock allocateSuperBlock(final JavaController controller, final XcosDiagram parentGraph, final Object[] selection) throws ScilabInterpreterManagement.InterpreterException {
423         final SuperBlock superBlock = (SuperBlock) XcosCellFactory.createBlock(INTERFUNCTION_NAME);
424
425         /*
426          * Remove the default allocated ports
427          */
428         while (superBlock.getChildCount() > 0) {
429             superBlock.remove(superBlock.getChildCount() - 1);
430         }
431         VectorOfScicosID children = new VectorOfScicosID();
432         controller.setObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.CHILDREN, children);
433
434         /*
435          * Place the super block
436          */
437         final mxRectangle dims = parentGraph.getBoundingBoxFromGeometry(selection);
438         final double minX = dims.getX();
439         final double maxX = minX + dims.getWidth();
440
441         final double minY = dims.getY();
442         final double maxY = minY + dims.getHeight();
443
444         mxGeometry geom = superBlock.getGeometry();
445         geom.setX((maxX + minX - geom.getWidth()) / 2.0);
446         geom.setY((maxY + minY - geom.getHeight()) / 2.0);
447         superBlock.setGeometry(geom);
448
449         /*
450          * get statistics to flip and rotate
451          */
452         VectorOfDouble mvcAngle = new VectorOfDouble();
453
454         int angleCounter = 0;
455         int flipCounter = 0;
456         int mirrorCounter = 0;
457         for (Object object : selection) {
458             if (object instanceof BasicBlock) {
459                 final BasicBlock b = (BasicBlock) object;
460
461                 String[] style = new String[1];
462                 controller.getObjectProperty(b.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
463                 StyleMap styleMap = new StyleMap(style[0]);
464
465                 final boolean mirrored = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
466                 final boolean flipped = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
467                 final int intRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0")).intValue();
468
469                 angleCounter += intRotation;
470                 if (flipped) {
471                     flipCounter++;
472                 }
473                 if (mirrored) {
474                     mirrorCounter++;
475                 }
476             }
477         }
478
479         /*
480          * apply statistics to flip and rotate
481          */
482         final int halfSize = selection.length / 2;
483         String[] style = new String[1];
484         controller.getObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.STYLE, style);
485         StyleMap styleMap = new StyleMap(style[0]);
486
487         styleMap.put(XcosConstants.STYLE_ROTATION, Integer.toString(BlockPositioning.roundAngle(angleCounter / selection.length)));
488         if (flipCounter > halfSize) {
489             styleMap.put(XcosConstants.STYLE_FLIP, Boolean.toString(true));
490         }
491         if (mirrorCounter > halfSize) {
492             styleMap.put(XcosConstants.STYLE_MIRROR, Boolean.toString(true));
493         }
494
495         controller.setObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.STYLE, styleMap.toString());
496
497         return superBlock;
498     }
499
500     /**
501      * Create child cells and add them to the parent diagram. All links are also
502      * reconnected
503      *
504      * @param parentGraph
505      *            the parent diagram
506      * @param superBlock
507      *            the superblock
508      * @param inSelectionCells
509      *            the cells in the selection
510      * @return the broken descriptor set
511      */
512     private Collection<Broken> updateParent(final JavaController controller, final XcosDiagram parentGraph, final SuperBlock superBlock, final List<XcosCell> inSelectionCells) {
513         final Collection<Broken> brokenLinks;
514         final mxGraphModel parentModel = (mxGraphModel) parentGraph.getModel();
515
516         parentModel.beginUpdate();
517         try {
518             /*
519              * Add the internal links and fill border links Sort the broken
520              * links by position (to perform a good numbering order) and keep
521              * only one occurrence of a broken link.
522              */
523             brokenLinks = new TreeSet<>();
524             fillLinks(parentModel, inSelectionCells, brokenLinks);
525
526             /*
527              * Disconnect the broken links
528              */
529             for (Broken broken : brokenLinks) {
530                 mxGraphModel.setTerminals(parentModel, broken.getParentLink(), null, null);
531             }
532
533             /*
534              * Add the super block
535              */
536             parentGraph.addCell(superBlock);
537
538             /*
539              * Main broken loop
540              */
541             // ordering access is : IN, OUT, e_IN, e_OUT
542             final int[] ordering = { 0, 0, 0, 0 };
543             for (Broken broken : brokenLinks) {
544
545                 // set the ordering
546                 incrementOrdering(controller, ordering, broken);
547
548                 connectParent(parentGraph, parentModel, superBlock, broken);
549                 connectChild(parentGraph, parentModel, broken);
550
551                 /*
552                  * Update the view
553                  */
554                 BlockPositioning.updateBlockView(parentGraph, broken.getChildBlock());
555             }
556         } finally {
557             parentModel.endUpdate();
558         }
559
560         return brokenLinks;
561     }
562
563     /**
564      * Fill the internalLinks links from a selected block to a selected block
565      * and broken links with a broken object. Also disconnect all the broken
566      * links.
567      *
568      * @param parentModel
569      *            the model
570      * @param inSelectionCells
571      *            the selected blocks (with fast contains operation)
572      * @param brokenLinks
573      *            the broken links to find.
574      */
575     private void fillLinks(final mxGraphModel parentModel, final Collection<XcosCell> inSelectionCells, final Collection<Broken> brokenLinks) {
576         final Queue<Object> loopQueue = new LinkedList<>(inSelectionCells);
577
578         while (!loopQueue.isEmpty()) {
579             final Object cell = loopQueue.remove();
580
581             final int childCount = parentModel.getChildCount(cell);
582             for (int i = 0; i < childCount; i++) {
583                 final Object port = parentModel.getChildAt(cell, i);
584
585                 final int edgeCount = parentModel.getEdgeCount(port);
586                 for (int j = 0; j < edgeCount; j++) {
587                     final Object edge = parentModel.getEdgeAt(port, j);
588
589                     final Object source = parentModel.getTerminal(edge, true);
590                     final Object target = parentModel.getTerminal(edge, false);
591
592                     /*
593                      * Add the links
594                      */
595                     final boolean containsSource = inSelectionCells.contains(parentModel.getParent(source));
596                     final boolean containsTarget = inSelectionCells.contains(parentModel.getParent(target));
597
598                     if (containsSource && containsTarget && edge instanceof XcosCell) {
599                         inSelectionCells.add((XcosCell) edge);
600                         continue;
601                     }
602
603                     /*
604                      * Handle a broken link case
605                      */
606
607                     if (containsSource) {
608                         brokenLinks.add(new Broken(parentModel, (BasicPort) source, (BasicPort) target, (BasicLink) edge, true));
609                         continue;
610                     }
611
612                     if (containsTarget) {
613                         brokenLinks.add(new Broken(parentModel, (BasicPort) source, (BasicPort) target, (BasicLink) edge, false));
614                         continue;
615                     }
616                 }
617             }
618         }
619     }
620
621     /**
622      * Increment and set the ordering to the broken entry.
623      *
624      * @param controller the controller
625      * @param ordering
626      *            4x1 array of ordering
627      * @param broken
628      *            the current broken entry.
629      */
630     // CSOFF: MagicNumber
631     private void incrementOrdering(JavaController controller, final int[] ordering, Broken broken) {
632         if (broken.getTerminal() instanceof InputPort) {
633             broken.setOrdering(controller, ++ordering[0]);
634         } else if (broken.getTerminal() instanceof OutputPort) {
635             broken.setOrdering(controller, ++ordering[1]);
636         } else if (broken.getTerminal() instanceof ControlPort) {
637             broken.setOrdering(controller, ++ordering[2]);
638         } else { // if (broken.getTerminal() instanceof CommandPort)
639             broken.setOrdering(controller, ++ordering[3]);
640         }
641     }
642
643     // CSON: MagicNumber
644
645     /**
646      * Add I/O port and reconnect them
647      *
648      * @param parentGraph
649      *            the parent graph
650      * @param parentModel
651      *            the parent graph model
652      * @param superBlock
653      *            the super block
654      * @param broken
655      *            the broken entry
656      */
657     private void connectParent(final XcosDiagram parentGraph, final mxGraphModel parentModel, final SuperBlock superBlock, Broken broken) {
658         parentGraph.addCell(broken.getParentPort(), superBlock);
659         parentGraph.addCell(broken.getParentLink());
660
661         final mxICell source = broken.getParentTerminal(true);
662         final mxICell target = broken.getParentTerminal(false);
663
664         // then connect the link
665         mxGraphModel.setTerminals(parentModel, broken.getParentLink(), source, target);
666     }
667
668     /**
669      * Add I/O blocks and reconnect them
670      *
671      * @param childGraph
672      *            the child graph
673      * @param childModel
674      *            the child graph model
675      * @param broken
676      *            the broken entry
677      */
678     private void connectChild(final XcosDiagram childGraph, final mxGraphModel childModel, Broken broken) {
679         childGraph.addCell(broken.getChildBlock());
680         childGraph.addCell(broken.getChildLink());
681
682         final mxICell source = broken.getChildTerminal(true);
683         final mxICell target = broken.getChildTerminal(false);
684
685         // then connect the link
686         mxGraphModel.setTerminals(childModel, broken.getChildLink(), source, target);
687     }
688
689     /**
690      * Move the cells to the child graph
691      *
692      * @param parentGraph
693      *            the parent graph
694      * @param superBlock
695      *            the superBlock
696      * @param brokenLinks
697      *            the broken links set
698      * @param inSelectionCells
699      *            the cells in selection
700      * @return the superblock child diagram
701      */
702     private void moveToChild(final JavaController controller, final XcosDiagram parentGraph, final SuperBlock superBlock, final Collection<Broken> brokenLinks, final List<XcosCell> inSelectionCells) {
703         final Collection<XcosCell> cellsToCopy = new ArrayList<>();
704
705         /*
706          * create a collection with all the cells to move
707          */
708         cellsToCopy.addAll(inSelectionCells);
709         for (Broken b : brokenLinks) {
710             cellsToCopy.add(b.getChildBlock());
711             cellsToCopy.add(b.getChildLink());
712         }
713
714         /*
715          * Really move the cells
716          */
717         parentGraph.removeCells(cellsToCopy.toArray(), false);
718
719         VectorOfScicosID children = new VectorOfScicosID(cellsToCopy.size());
720         LongBuffer childrenUIDs = children.asByteBuffer(0, cellsToCopy.size()).asLongBuffer();
721
722         final long parentBlock = superBlock.getUID();
723         final long parentDiagram;
724         if (parentGraph.getKind() == Kind.DIAGRAM) {
725             parentDiagram = parentGraph.getUID();
726         } else {
727             parentDiagram = parentGraph.getRootDiagram().getUID();
728         }
729
730         cellsToCopy.stream().forEach(c -> {
731             childrenUIDs.put(c.getUID());
732             controller.referenceObject(c.getUID());
733
734             controller.setObjectProperty(c.getUID(), c.getKind(), ObjectProperties.PARENT_BLOCK, parentBlock);
735             controller.setObjectProperty(c.getUID(), c.getKind(), ObjectProperties.PARENT_DIAGRAM, parentDiagram);
736         });
737
738         controller.setObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.CHILDREN, children);
739
740
741         /*
742          * Translate the cells to the origin
743          *
744          * In this algorithm only block position are handled to avoid any
745          * placement issue and a static margin is added to avoid
746          * misplacement.
747          */
748         final double margin = 10.0;
749         final mxPoint orig = new mxPoint(Double.MAX_VALUE, Double.MAX_VALUE);
750         for (XcosCell c : cellsToCopy) {
751             if (c instanceof BasicBlock) {
752                 final mxGeometry geom = c.getGeometry();
753
754                 orig.setX(Math.min(geom.getX(), orig.getX()));
755                 orig.setY(Math.min(geom.getY(), orig.getY()));
756             }
757         }
758
759         // move the cells
760         final VectorOfDouble geometry = new VectorOfDouble();
761         cellsToCopy.stream().forEach(c -> {
762             controller.getObjectProperty(c.getUID(), c.getKind(), ObjectProperties.GEOMETRY, geometry);
763
764             geometry.set(0, geometry.get(0) - orig.getX() + margin);
765             geometry.set(1, geometry.get(1) - orig.getY() + margin);
766
767             controller.setObjectProperty(c.getUID(), c.getKind(), ObjectProperties.GEOMETRY, geometry);
768         });
769     }
770 }
771 // CSON: ClassFanOutComplexity