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.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;
69 * Move the Selected cells to a new SuperBlock diagram
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;
83 private static final String INTERFUNCTION_NAME = "SUPER_f";
91 public RegionToSuperblockAction(ScilabGraph scilabGraph) {
100 public static MenuItem createMenu(ScilabGraph scilabGraph) {
101 return createMenu(scilabGraph, RegionToSuperblockAction.class);
105 * Represent a broken link at selection time
107 private static final class Broken implements Comparable<Broken> {
108 private final mxGraphModel parentModel;
110 private final BasicPort source;
111 private final BasicPort target;
113 private final BasicLink parentLink;
114 private final boolean containsSource;
117 * Out of selection port position
119 private final double x;
120 private final double y;
123 * lazy allocated fields
125 private BasicLink childLink;
126 private BasicPort parentPort;
127 private ContextUpdate childBlock;
130 * Default Constructor
142 * @param containsSource
143 * is the source in selection
145 public Broken(XcosDiagram parentGraph, mxGraphModel parentModel, BasicPort source, BasicPort target, BasicLink link, boolean containsSource) {
147 this.parentModel = parentModel;
149 this.source = source;
150 this.target = target;
151 this.parentLink = link;
152 this.containsSource = containsSource;
154 final BasicPort terminal;
155 if (containsSource) {
164 final mxGeometry pos = parentModel.getGeometry(terminal);
165 final mxGeometry parent = parentModel.getGeometry(parentModel.getParent(terminal));
166 if (pos != null && parent != null) {
167 this.x = parentGraph.snap(pos.getX() + parent.getX() - (pos.getHeight() / 2));
168 this.y = parentGraph.snap(pos.getY() + parent.getY() - (pos.getWidth() / 2));
176 * @return the link on the parent diagram
178 public BasicLink getParentLink() {
184 * is a source port needed
185 * @return the parent terminal (source or not)
187 public mxICell getParentTerminal(boolean isSource) {
190 if (containsSource) {
192 ret = getParentPort();
200 ret = getParentPort();
209 * is a source port needed
210 * @return the child terminal (source or not)
212 public mxICell getChildTerminal(boolean isSource) {
215 if (containsSource) {
219 ret = getChildBlock().getChildAt(0);
223 ret = getChildBlock().getChildAt(0);
233 * @return the terminal in the selection
235 public BasicPort getTerminal() {
236 if (containsSource) {
244 * @return the link on the child diagram
246 public BasicLink getChildLink() {
247 if (childLink == null) {
248 childLink = (BasicLink) parentModel.cloneCells(new Object[] { parentLink }, true)[0];
254 * @return the port on the parent diagram
256 public BasicPort getParentPort() {
257 if (parentPort == null) {
258 JavaController controller = new JavaController();
259 long uid = controller.createObject(Kind.PORT);
262 if (containsSource) {
263 parentPort = IOBlocks.getOpposite(target.getClass())
264 .getConstructor(JavaController.class, Long.TYPE, Kind.class, Object.class, String.class, String.class)
265 .newInstance(controller, uid, Kind.PORT, null, null, new UID().toString());
267 parentPort = IOBlocks.getOpposite(source.getClass())
268 .getConstructor(JavaController.class, Long.TYPE, Kind.class, Object.class, String.class, String.class)
269 .newInstance(controller, uid, Kind.PORT, null, null, new UID().toString());;
271 } catch (ReflectiveOperationException e) {
272 Logger.getLogger(RegionToSuperblockAction.class.getName()).severe(e.toString());
273 } catch (IllegalArgumentException ex) {
274 Logger.getLogger(RegionToSuperblockAction.class.getName()).log(Level.SEVERE, null, ex);
282 * @return the child block to put on the child diagram
284 public ContextUpdate getChildBlock() {
285 if (childBlock == null) {
286 final BasicPort terminal = getTerminal();
287 childBlock = IOBlocks.createBlock(terminal);
290 * Set the child position
292 mxGeometry geom = childBlock.getGeometry();
295 childBlock.setGeometry(geom);
302 * Set the ordering on the I/O block and port
305 * the ordering to set
307 public void setOrdering(JavaController controller, int ordering) {
308 // update the child ordering
309 VectorOfInt ipar = new VectorOfInt(1);
310 ipar.set(0, ordering);
311 controller.setObjectProperty(getChildBlock().getUID(), getChildBlock().getKind(), ObjectProperties.IPAR, ipar);
313 VectorOfDouble exprs = new ScilabTypeCoder().var2vec(new ScilabString(Integer.toString(ordering)));
314 controller.setObjectProperty(getChildBlock().getUID(), getChildBlock().getKind(), ObjectProperties.EXPRS, exprs);
320 * This function is used to sort a {@link TreeSet} of {@link Broken}.
323 public int compareTo(Broken o) {
324 final int xdiff = (int) (x - o.x);
325 final int ydiff = (int) (y - o.y);
327 if (xdiff == 0 && ydiff == 0) {
328 // same position, sort by hashcode
329 return hashCode() - o.hashCode();
331 return (ydiff << (Integer.SIZE / 2)) + xdiff;
339 public String toString() {
340 final String labelSep = ": ";
341 final String link = " -> ";
343 final StringBuilder str = new StringBuilder();
344 if (getParentLink().getChildCount() > 0) {
346 str.append(getParentLink().getChildAt(0).getValue());
347 str.append(labelSep);
350 str.append(getParentTerminal(true));
352 str.append(getParentTerminal(false));
356 if (getChildLink().getChildCount() > 0) {
358 str.append(getChildLink().getChildAt(0).getValue());
359 str.append(labelSep);
362 str.append(getChildTerminal(true));
364 str.append(getChildTerminal(false));
366 return str.toString();
374 public void actionPerformed(ActionEvent e) {
375 final XcosDiagram parentGraph = (XcosDiagram) getGraph(e);
377 // action disabled when the cell is edited
378 final ScilabComponent comp = ((ScilabComponent) parentGraph.getAsComponent());
379 if (comp.isEditing()) {
383 parentGraph.info(XcosMessages.GENERATE_SUPERBLOCK);
384 parentGraph.getModel().beginUpdate();
386 final JavaController controller = new JavaController();
387 final SuperBlock superBlock;
388 final Collection<Broken> brokenLinks;
391 * Allocate superBlock
393 final Object[] selection = parentGraph.getSelectionCells();
394 List<XcosCell> toBeMoved = Arrays.stream(selection)
395 .filter(o -> o instanceof XcosCell)
396 .map(o -> (XcosCell) o)
399 superBlock = allocateSuperBlock(controller, parentGraph, selection);
402 * First perform all modification on the parent diagram to handle
403 * well undo/redo operations.
405 brokenLinks = updateParent(controller, parentGraph, superBlock, toBeMoved);
408 * Then move some cells to the child diagram
410 moveToChild(controller, parentGraph, superBlock, brokenLinks, toBeMoved);
412 BlockPositioning.updateBlockView(parentGraph, superBlock);
413 } catch (ScilabInterpreterManagement.InterpreterException ex) {
414 // Scilab seems to be blocked, just consume the exception at this point
416 parentGraph.getModel().endUpdate();
417 parentGraph.info(XcosMessages.EMPTY_INFO);
422 * Allocate a superBlock
427 * the selected blocks
428 * @return the allocated super block (without specific listeners)
430 private SuperBlock allocateSuperBlock(final JavaController controller, final XcosDiagram parentGraph, final Object[] selection) throws ScilabInterpreterManagement.InterpreterException {
431 final SuperBlock superBlock = (SuperBlock) XcosCellFactory.createBlock(INTERFUNCTION_NAME);
434 * Remove the default allocated ports
436 while (superBlock.getChildCount() > 0) {
437 superBlock.remove(superBlock.getChildCount() - 1);
439 VectorOfScicosID children = new VectorOfScicosID();
440 controller.setObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.CHILDREN, children);
443 * Place the super block
445 final mxRectangle dims = parentGraph.getBoundingBoxFromGeometry(selection);
446 final double minX = dims.getX();
447 final double maxX = minX + dims.getWidth();
449 final double minY = dims.getY();
450 final double maxY = minY + dims.getHeight();
452 mxGeometry geom = superBlock.getGeometry();
453 geom.setX((maxX + minX - geom.getWidth()) / 2.0);
454 geom.setY((maxY + minY - geom.getHeight()) / 2.0);
455 superBlock.setGeometry(geom);
458 * get statistics to flip and rotate
460 VectorOfDouble mvcAngle = new VectorOfDouble();
462 int angleCounter = 0;
464 int mirrorCounter = 0;
465 for (Object object : selection) {
466 if (object instanceof BasicBlock) {
467 final BasicBlock b = (BasicBlock) object;
469 String[] style = new String[1];
470 controller.getObjectProperty(b.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
471 StyleMap styleMap = new StyleMap(style[0]);
473 final boolean mirrored = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
474 final boolean flipped = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
475 final int intRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0")).intValue();
477 angleCounter += intRotation;
488 * apply statistics to flip and rotate
490 final int halfSize = selection.length / 2;
491 String[] style = new String[1];
492 controller.getObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.STYLE, style);
493 StyleMap styleMap = new StyleMap(style[0]);
495 styleMap.put(XcosConstants.STYLE_ROTATION, Integer.toString(BlockPositioning.roundAngle(angleCounter / selection.length)));
496 if (flipCounter > halfSize) {
497 styleMap.put(XcosConstants.STYLE_FLIP, Boolean.toString(true));
499 if (mirrorCounter > halfSize) {
500 styleMap.put(XcosConstants.STYLE_MIRROR, Boolean.toString(true));
503 controller.setObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.STYLE, styleMap.toString());
509 * Create child cells and add them to the parent diagram. All links are also
516 * @param inSelectionCells
517 * the cells in the selection
518 * @return the broken descriptor set
520 private Collection<Broken> updateParent(final JavaController controller, final XcosDiagram parentGraph, final SuperBlock superBlock, final List<XcosCell> inSelectionCells) {
521 final Collection<Broken> brokenLinks;
522 final mxGraphModel parentModel = (mxGraphModel) parentGraph.getModel();
524 parentModel.beginUpdate();
527 * Add the internal links and fill border links Sort the broken
528 * links by position (to perform a good numbering order) and keep
529 * only one occurrence of a broken link.
531 brokenLinks = new TreeSet<>();
532 fillLinks(parentGraph, parentModel, inSelectionCells, brokenLinks);
535 * Disconnect the broken links
537 for (Broken broken : brokenLinks) {
538 mxGraphModel.setTerminals(parentModel, broken.getParentLink(), null, null);
542 * Add the super block
544 parentGraph.addCell(superBlock);
549 // ordering access is : IN, OUT, e_IN, e_OUT
550 final int[] ordering = { 0, 0, 0, 0 };
551 for (Broken broken : brokenLinks) {
554 incrementOrdering(controller, ordering, broken);
556 connectParent(parentGraph, parentModel, superBlock, broken);
557 connectChild(parentGraph, parentModel, broken);
562 BlockPositioning.updateBlockView(parentGraph, broken.getChildBlock());
565 parentModel.endUpdate();
572 * Fill the internalLinks links from a selected block to a selected block
573 * and broken links with a broken object. Also disconnect all the broken
580 * @param inSelectionCells
581 * the selected blocks (with fast contains operation)
583 * the broken links to find.
585 private void fillLinks(final XcosDiagram parentGraph, final mxGraphModel parentModel, final Collection<XcosCell> inSelectionCells, final Collection<Broken> brokenLinks) {
586 final Queue<Object> loopQueue = new LinkedList<>(inSelectionCells);
588 while (!loopQueue.isEmpty()) {
589 final Object cell = loopQueue.remove();
591 final int childCount = parentModel.getChildCount(cell);
592 for (int i = 0; i < childCount; i++) {
593 final Object port = parentModel.getChildAt(cell, i);
595 final int edgeCount = parentModel.getEdgeCount(port);
596 for (int j = 0; j < edgeCount; j++) {
597 final Object edge = parentModel.getEdgeAt(port, j);
599 final Object source = parentModel.getTerminal(edge, true);
600 final Object target = parentModel.getTerminal(edge, false);
605 final boolean containsSource = inSelectionCells.contains(parentModel.getParent(source));
606 final boolean containsTarget = inSelectionCells.contains(parentModel.getParent(target));
608 if (containsSource && containsTarget && edge instanceof XcosCell) {
609 inSelectionCells.add((XcosCell) edge);
614 * Handle a broken link case
617 if (containsSource) {
618 brokenLinks.add(new Broken(parentGraph, parentModel, (BasicPort) source, (BasicPort) target, (BasicLink) edge, true));
622 if (containsTarget) {
623 brokenLinks.add(new Broken(parentGraph, parentModel, (BasicPort) source, (BasicPort) target, (BasicLink) edge, false));
632 * Increment and set the ordering to the broken entry.
634 * @param controller the controller
636 * 4x1 array of ordering
638 * the current broken entry.
640 // CSOFF: MagicNumber
641 private void incrementOrdering(JavaController controller, final int[] ordering, Broken broken) {
642 if (broken.getTerminal() instanceof InputPort) {
643 broken.setOrdering(controller, ++ordering[0]);
644 } else if (broken.getTerminal() instanceof OutputPort) {
645 broken.setOrdering(controller, ++ordering[1]);
646 } else if (broken.getTerminal() instanceof ControlPort) {
647 broken.setOrdering(controller, ++ordering[2]);
648 } else { // if (broken.getTerminal() instanceof CommandPort)
649 broken.setOrdering(controller, ++ordering[3]);
656 * Add I/O port and reconnect them
661 * the parent graph model
667 private void connectParent(final XcosDiagram parentGraph, final mxGraphModel parentModel, final SuperBlock superBlock, Broken broken) {
668 parentGraph.addCell(broken.getParentPort(), superBlock);
669 parentGraph.addCell(broken.getParentLink());
671 final mxICell source = broken.getParentTerminal(true);
672 final mxICell target = broken.getParentTerminal(false);
674 // then connect the link
675 mxGraphModel.setTerminals(parentModel, broken.getParentLink(), source, target);
679 * Add I/O blocks and reconnect them
684 * the child graph model
688 private void connectChild(final XcosDiagram childGraph, final mxGraphModel childModel, Broken broken) {
689 childGraph.addCell(broken.getChildBlock());
690 childGraph.addCell(broken.getChildLink());
692 final mxICell source = broken.getChildTerminal(true);
693 final mxICell target = broken.getChildTerminal(false);
695 // then connect the link
696 mxGraphModel.setTerminals(childModel, broken.getChildLink(), source, target);
700 * Move the cells to the child graph
707 * the broken links set
708 * @param inSelectionCells
709 * the cells in selection
710 * @return the superblock child diagram
712 private void moveToChild(final JavaController controller, final XcosDiagram parentGraph, final SuperBlock superBlock, final Collection<Broken> brokenLinks, final List<XcosCell> inSelectionCells) {
713 final Collection<XcosCell> cellsToCopy = new ArrayList<>();
716 * create a collection with all the cells to move
718 cellsToCopy.addAll(inSelectionCells);
719 for (Broken b : brokenLinks) {
720 cellsToCopy.add(b.getChildBlock());
721 cellsToCopy.add(b.getChildLink());
725 * Really move the cells
727 parentGraph.removeCells(cellsToCopy.toArray(), false);
729 VectorOfScicosID children = new VectorOfScicosID(cellsToCopy.size());
730 LongBuffer childrenUIDs = children.asByteBuffer(0, cellsToCopy.size()).asLongBuffer();
732 final long parentBlock = superBlock.getUID();
733 final long parentDiagram;
734 if (parentGraph.getKind() == Kind.DIAGRAM) {
735 parentDiagram = parentGraph.getUID();
737 parentDiagram = parentGraph.getRootDiagramUID(controller);
740 cellsToCopy.stream().forEach(c -> {
741 childrenUIDs.put(c.getUID());
742 controller.referenceObject(c.getUID());
744 controller.setObjectProperty(c.getUID(), c.getKind(), ObjectProperties.PARENT_BLOCK, parentBlock);
745 controller.setObjectProperty(c.getUID(), c.getKind(), ObjectProperties.PARENT_DIAGRAM, parentDiagram);
748 controller.setObjectProperty(superBlock.getUID(), superBlock.getKind(), ObjectProperties.CHILDREN, children);
751 // CSON: ClassFanOutComplexity