2 * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3 * Copyright (C) 2010 - DIGITEO - Clement DAVID
4 * Copyright (C) 2011-2016 - Scilab Enterprises - Clement DAVID
6 * Copyright (C) 2012 - 2016 - Scilab Enterprises
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.
17 package org.scilab.modules.xcos.block.actions;
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;
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;
44 import com.mxgraph.model.mxGeometry;
45 import com.mxgraph.model.mxGraphModel;
46 import com.mxgraph.model.mxICell;
47 import com.mxgraph.util.mxRectangle;
48 import java.nio.LongBuffer;
49 import java.rmi.server.UID;
50 import java.util.ArrayList;
51 import java.util.HashMap;
52 import java.util.List;
54 import java.util.logging.Level;
55 import static java.util.stream.Collectors.toList;
56 import org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement;
57 import org.scilab.modules.graph.utils.StyleMap;
58 import org.scilab.modules.types.ScilabString;
59 import org.scilab.modules.xcos.JavaController;
60 import org.scilab.modules.xcos.Kind;
61 import org.scilab.modules.xcos.ObjectProperties;
62 import org.scilab.modules.xcos.VectorOfDouble;
63 import org.scilab.modules.xcos.VectorOfInt;
64 import org.scilab.modules.xcos.VectorOfScicosID;
65 import static org.scilab.modules.xcos.graph.XcosDiagram.EIN;
66 import static org.scilab.modules.xcos.graph.XcosDiagram.EOUT;
67 import static org.scilab.modules.xcos.graph.XcosDiagram.IN;
68 import static org.scilab.modules.xcos.graph.XcosDiagram.OUT;
69 import static org.scilab.modules.xcos.graph.XcosDiagram.UpdateSuperblockPortsTracker.syncPorts;
70 import org.scilab.modules.xcos.graph.model.XcosCell;
71 import org.scilab.modules.xcos.graph.model.XcosCellFactory;
72 import org.scilab.modules.xcos.io.ScilabTypeCoder;
73 import org.scilab.modules.xcos.utils.XcosConstants;
76 * Move the Selected cells to a new SuperBlock diagram
78 // CSOFF: ClassFanOutComplexity
79 @SuppressWarnings(value = { "serial" })
80 public class RegionToSuperblockAction extends VertexSelectionDependantAction {
81 /** Name of the action */
82 public static final String NAME = XcosMessages.REGION_TO_SUPERBLOCK;
83 /** Icon name of the action */
84 public static final String SMALL_ICON = "object-group";
85 /** Mnemonic key of the action */
86 public static final int MNEMONIC_KEY = 0;
87 /** Accelerator key for the action */
88 public static final int ACCELERATOR_KEY = 0;
90 private static final String INTERFUNCTION_NAME = "SUPER_f";
98 public RegionToSuperblockAction(ScilabGraph scilabGraph) {
107 public static MenuItem createMenu(ScilabGraph scilabGraph) {
108 return createMenu(scilabGraph, RegionToSuperblockAction.class);
112 * Represent a broken link at selection time
114 private static final class Broken implements Comparable<Broken> {
115 private final mxGraphModel parentModel;
117 private final BasicPort source;
118 private final BasicPort target;
120 private final BasicLink parentLink;
121 private final boolean containsSource;
124 * Out of selection port position
126 private final double x;
127 private final double y;
130 * lazy allocated fields
132 private BasicLink childLink;
133 private BasicPort parentPort;
134 private ContextUpdate childBlock;
137 * Default Constructor
149 * @param containsSource
150 * is the source in selection
152 public Broken(XcosDiagram parentGraph, mxGraphModel parentModel, BasicPort source, BasicPort target, BasicLink link, boolean containsSource) {
154 this.parentModel = parentModel;
156 this.source = source;
157 this.target = target;
158 this.parentLink = link;
159 this.containsSource = containsSource;
161 final BasicPort terminal;
162 if (containsSource) {
171 final mxGeometry pos = parentModel.getGeometry(terminal);
172 final mxGeometry parent = parentModel.getGeometry(parentModel.getParent(terminal));
173 if (pos != null && parent != null) {
174 this.x = parentGraph.snap(pos.getX() + parent.getX() - (pos.getHeight() / 2));
175 this.y = parentGraph.snap(pos.getY() + parent.getY() - (pos.getWidth() / 2));
183 * @return the link on the parent diagram
185 public BasicLink getParentLink() {
191 * is a source port needed
192 * @return the parent terminal (source or not)
194 public mxICell getParentTerminal(boolean isSource) {
197 if (containsSource) {
199 ret = getParentPort();
207 ret = getParentPort();
216 * is a source port needed
217 * @return the child terminal (source or not)
219 public mxICell getChildTerminal(boolean isSource) {
222 if (containsSource) {
226 ret = getChildBlock().getChildAt(0);
230 ret = getChildBlock().getChildAt(0);
240 * @return the terminal in the selection
242 public BasicPort getTerminal() {
243 if (containsSource) {
251 * @return the link on the child diagram
253 public BasicLink getChildLink() {
254 if (childLink == null) {
255 childLink = (BasicLink) parentModel.cloneCells(new Object[] { parentLink }, true)[0];
261 * @return the port on the parent diagram
263 public BasicPort getParentPort() {
264 if (parentPort == null) {
265 JavaController controller = new JavaController();
266 long uid = controller.createObject(Kind.PORT);
269 if (containsSource) {
270 parentPort = IOBlocks.getOpposite(target.getClass())
271 .getConstructor(JavaController.class, Long.TYPE, Kind.class, Object.class, String.class, String.class)
272 .newInstance(controller, uid, Kind.PORT, null, null, new UID().toString());
274 parentPort = IOBlocks.getOpposite(source.getClass())
275 .getConstructor(JavaController.class, Long.TYPE, Kind.class, Object.class, String.class, String.class)
276 .newInstance(controller, uid, Kind.PORT, null, null, new UID().toString());;
278 } catch (ReflectiveOperationException e) {
279 Logger.getLogger(RegionToSuperblockAction.class.getName()).severe(e.toString());
280 } catch (IllegalArgumentException ex) {
281 Logger.getLogger(RegionToSuperblockAction.class.getName()).log(Level.SEVERE, null, ex);
289 * @return the child block to put on the child diagram
291 public ContextUpdate getChildBlock() {
292 if (childBlock == null) {
293 final BasicPort terminal = getTerminal();
294 childBlock = IOBlocks.createBlock(terminal);
297 * Set the child position
299 mxGeometry geom = childBlock.getGeometry();
302 childBlock.setGeometry(geom);
309 * Set the ordering on the I/O block and port
312 * the ordering to set
314 public void setOrdering(JavaController controller, int ordering) {
315 // update the child ordering
316 VectorOfInt ipar = new VectorOfInt(1);
317 ipar.set(0, ordering);
318 controller.setObjectProperty(getChildBlock().getUID(), getChildBlock().getKind(), ObjectProperties.IPAR, ipar);
320 VectorOfDouble exprs = new ScilabTypeCoder().var2vec(new ScilabString(Integer.toString(ordering)));
321 controller.setObjectProperty(getChildBlock().getUID(), getChildBlock().getKind(), ObjectProperties.EXPRS, exprs);
327 * This function is used to sort a {@link TreeSet} of {@link Broken}.
330 public int compareTo(Broken o) {
331 final int xdiff = (int) (x - o.x);
332 final int ydiff = (int) (y - o.y);
334 if (xdiff == 0 && ydiff == 0) {
335 // same position, sort by hashcode
336 return hashCode() - o.hashCode();
338 return (ydiff << (Integer.SIZE / 2)) + xdiff;
346 public String toString() {
347 final String labelSep = ": ";
348 final String link = " -> ";
350 final StringBuilder str = new StringBuilder();
351 if (getParentLink().getChildCount() > 0) {
353 str.append(getParentLink().getChildAt(0).getValue());
354 str.append(labelSep);
357 str.append(getParentTerminal(true));
359 str.append(getParentTerminal(false));
363 if (getChildLink().getChildCount() > 0) {
365 str.append(getChildLink().getChildAt(0).getValue());
366 str.append(labelSep);
369 str.append(getChildTerminal(true));
371 str.append(getChildTerminal(false));
373 return str.toString();
381 public void actionPerformed(ActionEvent e) {
382 final XcosDiagram parentGraph = (XcosDiagram) getGraph(e);
384 // action disabled when the cell is edited
385 final ScilabComponent comp = ((ScilabComponent) parentGraph.getAsComponent());
386 if (comp.isEditing()) {
390 parentGraph.info(XcosMessages.GENERATE_SUPERBLOCK);
391 parentGraph.getModel().beginUpdate();
393 final JavaController controller = new JavaController();
394 final SuperBlock superBlock;
395 final Collection<Broken> brokenLinks;
398 * Allocate superBlock
400 final Object[] selection = parentGraph.getSelectionCells();
401 List<XcosCell> toBeMoved = Arrays.stream(selection)
402 .filter(o -> o instanceof XcosCell)
403 .map(o -> (XcosCell) o)
406 superBlock = allocateSuperBlock(controller, parentGraph, selection);
409 * First perform all modification on the parent diagram to handle
410 * well undo/redo operations.
412 brokenLinks = updateParent(controller, parentGraph, superBlock, toBeMoved);
415 * Then move some cells to the child diagram
417 moveToChild(controller, parentGraph, superBlock, brokenLinks, toBeMoved);
420 * Append the port to the superblock (in case of IN_f / OUT_f selected)
422 updateIO(controller, parentGraph, superBlock, toBeMoved);
424 BlockPositioning.updateBlockView(parentGraph, superBlock);
425 } catch (ScilabInterpreterManagement.InterpreterException ex) {
426 // Scilab seems to be blocked, just consume the exception at this point
428 parentGraph.getModel().endUpdate();
429 parentGraph.info(XcosMessages.EMPTY_INFO);
434 * Allocate a superBlock
439 * the selected blocks
440 * @return the allocated super block (without specific listeners)
442 private SuperBlock allocateSuperBlock(final JavaController controller, final XcosDiagram parentGraph, final Object[] selection) throws ScilabInterpreterManagement.InterpreterException {
443 final SuperBlock superBlock = (SuperBlock) XcosCellFactory.createBlock(INTERFUNCTION_NAME);
446 * Remove the default allocated ports
448 while (superBlock.getChildCount() > 0) {
449 superBlock.remove(superBlock.getChildCount() - 1);
451 VectorOfScicosID children = new VectorOfScicosID();
452 controller.setObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.CHILDREN, children);
455 * Place the super block
457 final mxRectangle dims = parentGraph.getBoundingBoxFromGeometry(selection);
458 final double minX = dims.getX();
459 final double maxX = minX + dims.getWidth();
461 final double minY = dims.getY();
462 final double maxY = minY + dims.getHeight();
464 mxGeometry geom = superBlock.getGeometry();
465 geom.setX((maxX + minX - geom.getWidth()) / 2.0);
466 geom.setY((maxY + minY - geom.getHeight()) / 2.0);
467 superBlock.setGeometry(geom);
470 * get statistics to flip and rotate
472 VectorOfDouble mvcAngle = new VectorOfDouble();
474 int angleCounter = 0;
476 int mirrorCounter = 0;
477 for (Object object : selection) {
478 if (object instanceof BasicBlock) {
479 final BasicBlock b = (BasicBlock) object;
481 String[] style = new String[1];
482 controller.getObjectProperty(b.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
483 StyleMap styleMap = new StyleMap(style[0]);
485 final boolean mirrored = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
486 final boolean flipped = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
487 final int intRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0")).intValue();
489 angleCounter += intRotation;
500 * apply statistics to flip and rotate
502 final int halfSize = selection.length / 2;
503 String[] style = new String[1];
504 controller.getObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.STYLE, style);
505 StyleMap styleMap = new StyleMap(style[0]);
507 styleMap.put(XcosConstants.STYLE_ROTATION, Integer.toString(BlockPositioning.roundAngle(angleCounter / selection.length)));
508 if (flipCounter > halfSize) {
509 styleMap.put(XcosConstants.STYLE_FLIP, Boolean.toString(true));
511 if (mirrorCounter > halfSize) {
512 styleMap.put(XcosConstants.STYLE_MIRROR, Boolean.toString(true));
515 controller.setObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.STYLE, styleMap.toString());
521 * Create child cells and add them to the parent diagram. All links are also
528 * @param inSelectionCells
529 * the cells in the selection
530 * @return the broken descriptor set
532 private Collection<Broken> updateParent(final JavaController controller, final XcosDiagram parentGraph, final SuperBlock superBlock, final List<XcosCell> inSelectionCells) {
533 final Collection<Broken> brokenLinks;
534 final mxGraphModel parentModel = (mxGraphModel) parentGraph.getModel();
536 parentModel.beginUpdate();
539 * Add the internal links and fill border links Sort the broken
540 * links by position (to perform a good numbering order) and keep
541 * only one occurrence of a broken link.
543 brokenLinks = new TreeSet<>();
544 fillLinks(parentGraph, parentModel, inSelectionCells, brokenLinks);
547 * Disconnect the broken links
549 for (Broken broken : brokenLinks) {
550 mxGraphModel.setTerminals(parentModel, broken.getParentLink(), null, null);
554 * Add the super block
556 parentGraph.addCell(superBlock);
561 // ordering access is : IN, OUT, e_IN, e_OUT
562 final int[] ordering = { 0, 0, 0, 0 };
563 for (Broken broken : brokenLinks) {
566 incrementOrdering(controller, ordering, broken);
568 connectParent(parentGraph, parentModel, superBlock, broken);
569 connectChild(parentGraph, parentModel, broken);
574 BlockPositioning.updateBlockView(parentGraph, broken.getChildBlock());
577 parentModel.endUpdate();
584 * Fill the internalLinks links from a selected block to a selected block
585 * and broken links with a broken object. Also disconnect all the broken
592 * @param inSelectionCells
593 * the selected blocks (with fast contains operation)
595 * the broken links to find.
597 private void fillLinks(final XcosDiagram parentGraph, final mxGraphModel parentModel, final Collection<XcosCell> inSelectionCells, final Collection<Broken> brokenLinks) {
598 final Queue<Object> loopQueue = new LinkedList<>(inSelectionCells);
600 while (!loopQueue.isEmpty()) {
601 final Object cell = loopQueue.remove();
603 final int childCount = parentModel.getChildCount(cell);
604 for (int i = 0; i < childCount; i++) {
605 final Object port = parentModel.getChildAt(cell, i);
607 final int edgeCount = parentModel.getEdgeCount(port);
608 for (int j = 0; j < edgeCount; j++) {
609 final Object edge = parentModel.getEdgeAt(port, j);
611 final Object source = parentModel.getTerminal(edge, true);
612 final Object target = parentModel.getTerminal(edge, false);
617 final boolean containsSource = inSelectionCells.contains(parentModel.getParent(source));
618 final boolean containsTarget = inSelectionCells.contains(parentModel.getParent(target));
620 if (containsSource && containsTarget && edge instanceof XcosCell) {
621 inSelectionCells.add((XcosCell) edge);
626 * Handle a broken link case
629 if (containsSource) {
630 brokenLinks.add(new Broken(parentGraph, parentModel, (BasicPort) source, (BasicPort) target, (BasicLink) edge, true));
634 if (containsTarget) {
635 brokenLinks.add(new Broken(parentGraph, parentModel, (BasicPort) source, (BasicPort) target, (BasicLink) edge, false));
644 * Increment and set the ordering to the broken entry.
646 * @param controller the controller
648 * 4x1 array of ordering
650 * the current broken entry.
652 // CSOFF: MagicNumber
653 private void incrementOrdering(JavaController controller, final int[] ordering, Broken broken) {
654 if (broken.getTerminal() instanceof InputPort) {
655 broken.setOrdering(controller, ++ordering[0]);
656 } else if (broken.getTerminal() instanceof OutputPort) {
657 broken.setOrdering(controller, ++ordering[1]);
658 } else if (broken.getTerminal() instanceof ControlPort) {
659 broken.setOrdering(controller, ++ordering[2]);
660 } else { // if (broken.getTerminal() instanceof CommandPort)
661 broken.setOrdering(controller, ++ordering[3]);
668 * Add I/O port and reconnect them
673 * the parent graph model
679 private void connectParent(final XcosDiagram parentGraph, final mxGraphModel parentModel, final SuperBlock superBlock, Broken broken) {
680 parentGraph.addCell(broken.getParentPort(), superBlock);
681 parentGraph.addCell(broken.getParentLink());
683 final mxICell source = broken.getParentTerminal(true);
684 final mxICell target = broken.getParentTerminal(false);
686 // then connect the link
687 mxGraphModel.setTerminals(parentModel, broken.getParentLink(), source, target);
691 * Add I/O blocks and reconnect them
696 * the child graph model
700 private void connectChild(final XcosDiagram childGraph, final mxGraphModel childModel, Broken broken) {
701 childGraph.addCell(broken.getChildBlock());
702 childGraph.addCell(broken.getChildLink());
704 final mxICell source = broken.getChildTerminal(true);
705 final mxICell target = broken.getChildTerminal(false);
707 // then connect the link
708 mxGraphModel.setTerminals(childModel, broken.getChildLink(), source, target);
712 * Move the cells to the child graph
719 * the broken links set
720 * @param inSelectionCells
721 * the cells in selection
722 * @return the superblock child diagram
724 private void moveToChild(final JavaController controller, final XcosDiagram parentGraph, final SuperBlock superBlock, final Collection<Broken> brokenLinks, final List<XcosCell> inSelectionCells) {
725 final Collection<XcosCell> cellsToCopy = new ArrayList<>();
728 * create a collection with all the cells to move
730 cellsToCopy.addAll(inSelectionCells);
731 for (Broken b : brokenLinks) {
732 cellsToCopy.add(b.getChildBlock());
733 cellsToCopy.add(b.getChildLink());
737 * Really move the cells
739 parentGraph.removeCells(cellsToCopy.toArray(), false);
741 VectorOfScicosID children = new VectorOfScicosID(cellsToCopy.size());
742 LongBuffer childrenUIDs = children.asByteBuffer(0, cellsToCopy.size()).asLongBuffer();
744 final long parentBlock = superBlock.getUID();
745 final long parentDiagram;
746 if (parentGraph.getKind() == Kind.DIAGRAM) {
747 parentDiagram = parentGraph.getUID();
749 parentDiagram = parentGraph.getRootDiagramUID(controller);
752 cellsToCopy.stream().forEach(c -> {
753 childrenUIDs.put(c.getUID());
754 controller.referenceObject(c.getUID());
756 controller.setObjectProperty(c.getUID(), c.getKind(), ObjectProperties.PARENT_BLOCK, parentBlock);
757 controller.setObjectProperty(c.getUID(), c.getKind(), ObjectProperties.PARENT_DIAGRAM, parentDiagram);
760 controller.setObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.CHILDREN, children);
764 * Update the ports according to the IOBlocks moved
765 * @param controller the shared controller
766 * @param parent the parent diagram
767 * @param superblock the superblock cell
768 * @param toBeMoved the moved blocks
770 private void updateIO(JavaController controller, XcosDiagram parent, SuperBlock superblock, List<XcosCell> toBeMoved) {
771 Map<Object, Object> context = new HashMap<>();
772 XcosDiagram.UpdateSuperblockPortsTracker.updateContext(context, toBeMoved, controller);
774 syncPorts(controller, superblock, ObjectProperties.INPUTS, (List<ContextUpdate>) context.get(IN), parent);
775 syncPorts(controller, superblock, ObjectProperties.OUTPUTS, (List<ContextUpdate>) context.get(OUT), parent);
776 syncPorts(controller, superblock, ObjectProperties.EVENT_INPUTS, (List<ContextUpdate>) context.get(EIN), parent);
777 syncPorts(controller, superblock, ObjectProperties.EVENT_OUTPUTS, (List<ContextUpdate>) context.get(EOUT), parent);
780 // CSON: ClassFanOutComplexity