2 * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3 * Copyright (C) 2009 - DIGITEO - Antoine ELIAS
4 * Copyright (C) 2011-2015 - Scilab Enterprises - Clement DAVID
5 * Copyright (C) 2018 - ESI Group - Clement DAVID
7 * Copyright (C) 2012 - 2016 - Scilab Enterprises
9 * This file is hereby licensed under the terms of the GNU GPL v2.0,
10 * pursuant to article 5.3.4 of the CeCILL v.2.1.
11 * This file was originally licensed under the terms of the CeCILL v2.1,
12 * and continues to be available under such terms.
13 * For more information, see the COPYING file which you should have received
14 * along with this program.
18 package org.scilab.modules.xcos.utils;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.List;
25 import org.scilab.modules.xcos.JavaController;
26 import org.scilab.modules.xcos.Kind;
27 import org.scilab.modules.xcos.ObjectProperties;
28 import org.scilab.modules.xcos.block.BasicBlock;
29 import org.scilab.modules.xcos.graph.XcosDiagram;
30 import org.scilab.modules.xcos.io.scicos.BasicBlockInfo;
31 import org.scilab.modules.xcos.port.BasicPort;
32 import org.scilab.modules.xcos.port.Orientation;
34 import com.mxgraph.model.mxGeometry;
35 import com.mxgraph.model.mxIGraphModel;
36 import com.mxgraph.util.mxStyleUtils;
37 import org.scilab.modules.graph.utils.StyleMap;
40 * Helpers to place port on a block.
42 public final class BlockPositioning {
45 * The default grid size. This value is used when the grid size isn't accessible (on the palette).
47 public static final double DEFAULT_GRIDSIZE = Double.MIN_NORMAL;
48 /** The rotation step of the clockwise and anticlockwise rotation */
49 public static final int ROTATION_STEP = 90;
50 /** The max valid rotation value (always 360 degres) */
51 public static final int MAX_ROTATION = 360;
53 /** This class is a static singleton, thus it must not be instantiated */
54 private BlockPositioning() {
58 * Dispatch ports on Block's _WEST_ side.
61 * The block we have to work on.
63 * The ports we have to move on the side.
65 public static void updateWestPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
67 double gridSize = diag.getGridSize();
69 final mxGeometry blockGeom = block.getGeometry();
70 assert blockGeom != null;
71 final int portsSize = ports.size();
72 final double blockLength = blockGeom.getHeight();
73 final double segLength = blockLength / (portsSize + 1);
75 diag.getModel().beginUpdate();
76 for (int i = 0; i < portsSize; ++i) {
77 final BasicPort port = (ports.get(i));
78 final mxGeometry portGeom = (mxGeometry) port.getGeometry().clone();
80 double nonVariantPosition = -portGeom.getWidth();
82 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
84 portGeom.setX(nonVariantPosition);
85 portGeom.setY(alignedPosition);
87 diag.getModel().setGeometry(port, portGeom);
88 diag.getModel().setStyle(port, port.computeLabelPosition(Orientation.WEST));
90 diag.getModel().endUpdate();
94 * Calculate an aligned port position.
101 * the current working index
102 * @return the aligned position on the grid.
104 private static double calculateAlignedPosition(final double gridSize, final double segLength, int i) {
106 * The base position is the origin of the port geometry. It is the upper-left corner position.
108 final double basePosition = (i + 1) * segLength;
111 * The aligned base position is the base position aligned on the grid.
113 final double alignedBasePosition = basePosition - Math.IEEEremainder(basePosition, gridSize);
116 * The aligned position is the base position translated from origin to the middle of the port.
118 final double alignedPosition = alignedBasePosition - (BasicPort.DEFAULT_PORTSIZE / 2.0);
120 return alignedPosition;
124 * Dispatch ports on Block's _NORTH_ side.
127 * The block we have to work on.
129 * The ports we have to move on the side.
131 public static void updateNorthPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
132 double gridSize = diag.getGridSize();
134 final mxGeometry blockGeom = block.getGeometry();
135 assert blockGeom != null;
136 final int portsSize = ports.size();
137 final double blockLength = blockGeom.getWidth();
138 final double segLength = blockLength / (portsSize + 1);
140 diag.getModel().beginUpdate();
141 for (int i = 0; i < portsSize; ++i) {
142 final BasicPort port = (ports.get(i));
143 final mxGeometry portGeom = (mxGeometry) port.getGeometry().clone();
145 double nonVariantPosition = -portGeom.getHeight();
147 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
149 portGeom.setX(alignedPosition);
150 portGeom.setY(nonVariantPosition);
152 diag.getModel().setGeometry(port, portGeom);
153 diag.getModel().setStyle(port, port.computeLabelPosition(Orientation.NORTH));
155 diag.getModel().endUpdate();
159 * Dispatch ports on Block's _EAST_ side.
162 * The block we have to work on.
164 * The ports we have to move on the side.
166 public static void updateEastPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
167 double gridSize = diag.getGridSize();
169 final mxGeometry blockGeom = block.getGeometry();
170 assert blockGeom != null;
171 final int portsSize = ports.size();
172 final double blockLength = blockGeom.getHeight();
173 final double segLength = blockLength / (portsSize + 1);
175 diag.getModel().beginUpdate();
176 for (int i = 0; i < portsSize; ++i) {
177 final BasicPort port = (ports.get(i));
178 final mxGeometry portGeom = (mxGeometry) port.getGeometry().clone();
180 double nonVariantPosition = blockGeom.getWidth();
182 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
184 portGeom.setX(nonVariantPosition);
185 portGeom.setY(alignedPosition);
187 diag.getModel().setGeometry(port, portGeom);
188 diag.getModel().setStyle(port, port.computeLabelPosition(Orientation.EAST));
190 diag.getModel().endUpdate();
194 * Dispatch ports on Block's _SOUTH_ side.
197 * The block we have to work on.
199 * The ports we have to move on the side.
201 public static void updateSouthPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
202 double gridSize = diag.getGridSize();
204 final mxGeometry blockGeom = block.getGeometry();
205 assert blockGeom != null;
206 final int portsSize = ports.size();
207 final double blockLength = blockGeom.getWidth();
208 final double segLength = blockLength / (portsSize + 1);
210 diag.getModel().beginUpdate();
211 for (int i = 0; i < portsSize; ++i) {
212 final BasicPort port = (ports.get(i));
213 final mxGeometry portGeom = (mxGeometry) port.getGeometry().clone();
215 double nonVariantPosition = blockGeom.getHeight();
217 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
219 portGeom.setX(alignedPosition);
220 portGeom.setY(nonVariantPosition);
222 diag.getModel().setGeometry(port, portGeom);
223 diag.getModel().setStyle(port, port.computeLabelPosition(Orientation.SOUTH));
225 diag.getModel().endUpdate();
229 * Update all the port position of the block.
232 * The block we have to work on.
234 public static void updatePortsPosition(final XcosDiagram diag, BasicBlock block) {
235 final Map<Orientation, List<BasicPort>> ports = BasicBlockInfo.getAllOrientedPorts(block);
237 diag.getModel().beginUpdate();
238 for (Orientation iter : Orientation.values()) {
239 List<BasicPort> orientedPorts = ports.get(iter);
240 if (orientedPorts != null && !orientedPorts.isEmpty()) {
241 updatePortsPositions(diag, block, orientedPorts, iter);
244 diag.getModel().endUpdate();
248 * Update the port position for the specified orientation. This function manage the flip and mirror properties.
251 * The block we are working on
253 * The ports at with the specified orientation
257 private static void updatePortsPositions(final XcosDiagram diag, BasicBlock block, List<BasicPort> ports, Orientation iter) {
258 @SuppressWarnings("serial")
259 final List<BasicPort> invertedPorts = new ArrayList<BasicPort>(ports) {
261 Collections.reverse(this);
265 JavaController controller = new JavaController();
266 String[] style = new String[1];
267 controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
268 StyleMap styleMap = new StyleMap(style[0]);
270 final boolean mirrored = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
271 final boolean flipped = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
272 final int intRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0")).intValue();
273 final int angle = ((Math.round(intRotation)) % 360 + 360) % 360;
275 List<BasicPort> working = ports;
277 /* List order modification with the flip flag */
279 if (iter == Orientation.NORTH || iter == Orientation.SOUTH) {
280 working = invertedPorts;
284 /* List order modification with the mirror flag */
286 if (iter == Orientation.EAST || iter == Orientation.WEST) {
287 working = invertedPorts;
292 * Ugly modification of the iter to update at the right position Works only for 0 - 90 - 180 - 270 angles.
294 Orientation rotated = rotateOrientation(iter, mirrored, flipped);
296 updatePortsPosition(diag, block, rotated, angle, working);
300 * Ugly modification of the iter to update at the right position. Works only for 0 - 90 - 180 - 270 angles.
303 * the real orientation
305 * is the block mirrored
307 * is the block flipped
308 * @return The modified orientation
310 private static Orientation rotateOrientation(final Orientation iter, final boolean mirrored, final boolean flipped) {
311 final int nbOfOrientations = Orientation.values().length; // 4
312 Orientation rotated = iter;
314 /* Flip & Mirror management */
316 if (rotated == Orientation.EAST || rotated == Orientation.WEST) {
317 rotated = Orientation.values()[(rotated.ordinal() + 2) % nbOfOrientations];
321 if (rotated == Orientation.NORTH || rotated == Orientation.SOUTH) {
322 rotated = Orientation.values()[(rotated.ordinal() + 2) % nbOfOrientations];
329 * Update the ports positions according to the angle. This function doesn't handle order inversion.
332 * The block we are working on
334 * The current port orientation
336 * The angle we have to rotate
338 * The ordered ports we are working on.
340 private static void updatePortsPosition(final XcosDiagram diag, BasicBlock block, Orientation iter, final double angle, List<BasicPort> working) {
342 * Ugly modification of the iter to update at the right position Works only for 0 - 90 - 180 - 270 angles.
344 final int nbOfOrientations = Orientation.values().length; // 4
345 Orientation rotated = iter;
347 /* Angle management */
348 double rotationIndex = angle / ROTATION_STEP;
349 rotated = Orientation.values()[(rotated.ordinal() + (int) rotationIndex) % nbOfOrientations];
351 /* Call the associated function */
354 updateNorthPortsPosition(diag, block, working);
357 updateSouthPortsPosition(diag, block, working);
360 updateEastPortsPosition(diag, block, working);
363 updateWestPortsPosition(diag, block, working);
372 * Rotate all the port of the block.
375 * The block to work on.
377 public static void rotateAllPorts(final XcosDiagram diag, BasicBlock block) {
378 JavaController controller = new JavaController();
379 String[] style = new String[1];
380 controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
381 StyleMap styleMap = new StyleMap(style[0]);
383 final boolean mirrored = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
384 final boolean flipped = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
385 final int intRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0")).intValue();
386 final int angle = ((Math.round(intRotation)) % 360 + 360) % 360;
388 final int childrenCount = block.getChildCount();
389 for (int i = 0; i < childrenCount; ++i) {
390 if (block.getChildAt(i) instanceof BasicPort) {
391 final BasicPort port = (BasicPort) block.getChildAt(i);
392 final Orientation orientation = port.getOrientation();
394 diag.getModel().beginUpdate();
397 final mxIGraphModel model = diag.getModel();
398 final String rot = Integer.toString(orientation.getRelativeAngle(angle, port.getClass(), flipped, mirrored));
399 mxStyleUtils.setCellStyles(model, new Object[] { port }, XcosConstants.STYLE_ROTATION, rot);
401 diag.getModel().endUpdate();
407 * Update the geometry of the block's ports.
410 * The block to work on
412 public static void updateBlockView(final XcosDiagram diag, BasicBlock block) {
413 if (block.getKind() != Kind.BLOCK) {
417 diag.getModel().beginUpdate();
418 block.updateBlockView();
419 updatePortsPosition(diag, block);
420 rotateAllPorts(diag, block);
421 diag.getModel().endUpdate();
424 * TODO: bug #6705; This placement trick doesn't work on the first block Dnd as the view is not revalidated.
426 * On block loading, parentDiagram is null thus placement is not performed.
431 * Update the geometry of the block's ports.
432 * @param diag XcosDiagram
433 * @param blocks array with several blocks
435 public static void updatePortsPosition(final XcosDiagram diag, Object[] blocks) {
436 diag.getModel().beginUpdate();
437 for (Object b : blocks) {
438 BasicBlock block = (BasicBlock) b;
439 updatePortsPosition(diag, block);
441 diag.getModel().endUpdate();
445 * Flip a block (horizontal inversion).
448 * The block to work on
450 public static void toggleFlip(final XcosDiagram diag, BasicBlock block) {
451 StyleMap styleMap = new StyleMap(block.getStyle());
452 final boolean invertedFlip = ! Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
454 styleMap.put(XcosConstants.STYLE_FLIP, Boolean.toString(invertedFlip));
456 diag.getModel().setStyle(block, styleMap.toString());
457 updateBlockView(diag, block);
461 * Mirror a block (vertical inversion).
464 * The block to work on
466 public static void toggleMirror(final XcosDiagram diag, BasicBlock block) {
467 StyleMap styleMap = new StyleMap(block.getStyle());
468 final boolean invertedFlip = ! Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
470 styleMap.put(XcosConstants.STYLE_MIRROR, Boolean.toString(invertedFlip));
472 diag.getModel().setStyle(block, styleMap.toString());
473 updateBlockView(diag, block);
477 * Rotate a block with an anti-clockwise next value
480 * The block to work on
482 public static void toggleAntiClockwiseRotation(final XcosDiagram diag, BasicBlock block) {
483 StyleMap styleMap = new StyleMap(block.getStyle());
484 styleMap.put(XcosConstants.STYLE_ROTATION, Integer.toString(getNextAntiClockwiseAngle(styleMap)));
486 diag.getModel().setStyle(block, styleMap.toString());
487 updateBlockView(diag, block);
491 * Get the next anti-clockwise rotation value
495 * @return The angle value
497 public static int getNextAntiClockwiseAngle(StyleMap styleMap) {
498 final int intRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0")).intValue();
500 int angle = (intRotation - ROTATION_STEP + MAX_ROTATION) % MAX_ROTATION;
505 * Get the next clockwise rotation value
509 * @return The angle value
511 public static int getNextClockwiseAngle(StyleMap styleMap) {
512 final int intRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0")).intValue();
513 int angle = (intRotation + ROTATION_STEP) % MAX_ROTATION;
518 * Convert any angle value to a valid block value
521 * the non valid value
522 * @return the nearest graph valid value
524 public static int roundAngle(int angle) {
526 if (angle < 0 || angle > MAX_ROTATION) {
527 ret = (angle + MAX_ROTATION) % MAX_ROTATION;
530 for (int i = 0; i < (MAX_ROTATION / ROTATION_STEP); i++) {
531 int min = i * ROTATION_STEP;
532 int max = (i + 1) * ROTATION_STEP;
534 if (ret < (min + max) / 2) {